From a96642f6fda45710cd1601d9370322d557f26fe4 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Fri, 31 May 2019 18:38:25 -0700 Subject: [PATCH] Remove custom stream->pipe wrappers and make BodyReader/BodyWriter read-only (#10154) --- ...NetCore.Http.Abstractions.netcoreapp3.0.cs | 4 +- src/Http/Http.Abstractions/src/HttpRequest.cs | 4 +- .../Http.Abstractions/src/HttpResponse.cs | 4 +- .../HttpResponseWritingExtensionsTests.cs | 13 +- ...AspNetCore.Http.Features.netstandard2.0.cs | 4 +- .../src/IRequestBodyPipeFeature.cs | 2 +- .../src/IResponseBodyPipeFeature.cs | 2 +- src/Http/Http/perf/NoopStream.cs | 70 -- .../Http/perf/StreamPipeReaderBenchmark.cs | 82 -- .../Http/perf/StreamPipeWriterBenchmark.cs | 38 - ...Microsoft.AspNetCore.Http.netcoreapp3.0.cs | 98 +-- .../src/Features/RequestBodyPipeFeature.cs | 28 +- .../src/Features/ResponseBodyPipeFeature.cs | 28 +- .../Http/src/Internal/DefaultHttpRequest.cs | 1 - .../Http/src/Internal/DefaultHttpResponse.cs | 1 - src/Http/Http/src/ReadOnlyPipeStream.cs | 247 ------ src/Http/Http/src/StreamPipeReader.cs | 393 --------- src/Http/Http/src/StreamPipeReaderOptions.cs | 34 - src/Http/Http/src/StreamPipeWriter.cs | 347 -------- src/Http/Http/src/ThrowHelper.cs | 50 -- src/Http/Http/src/WriteOnlyPipeStream.cs | 169 ---- .../Http/test/Features/FormFeatureTests.cs | 13 +- .../Features/RequestBodyPipeFeatureTests.cs | 88 +- .../Features/ResponseBodyPipeFeatureTests.cs | 33 +- .../Http/test/FlushResultCancellationTests.cs | 30 - .../test/Internal/DefaultHttpRequestTests.cs | 30 - .../test/Internal/DefaultHttpResponseTests.cs | 29 - src/Http/Http/test/PipeStreamTest.cs | 85 -- src/Http/Http/test/PipeWriterTests.cs | 221 ----- .../Http/test/ReadAsyncCancellationTests.cs | 128 --- src/Http/Http/test/ReadOnlyPipeStreamTests.cs | 186 ----- .../Http/test/ReadingAdaptersInteropTests.cs | 147 ---- src/Http/Http/test/StreamPipeReaderTests.cs | 752 ------------------ src/Http/Http/test/StreamPipeTest.cs | 75 -- src/Http/Http/test/StreamPipeWriterTests.cs | 556 ------------- src/Http/Http/test/TestMemoryPool.cs | 206 ----- .../Http/test/WriteOnlyPipeStreamTests.cs | 206 ----- .../Http/test/WritingAdaptersInteropTests.cs | 155 ---- .../Http/HttpProtocol.FeatureCollection.cs | 81 +- .../Core/src/Internal/Http/HttpProtocol.cs | 18 +- .../src/Internal/Http/HttpRequestStream.cs | 192 ++++- .../src/Internal/Http/HttpResponseStream.cs | 119 ++- .../InMemory.FunctionalTests/RequestTests.cs | 24 - .../InMemory.FunctionalTests/ResponseTests.cs | 185 +---- 44 files changed, 386 insertions(+), 4792 deletions(-) delete mode 100644 src/Http/Http/perf/NoopStream.cs delete mode 100644 src/Http/Http/perf/StreamPipeReaderBenchmark.cs delete mode 100644 src/Http/Http/perf/StreamPipeWriterBenchmark.cs delete mode 100644 src/Http/Http/src/ReadOnlyPipeStream.cs delete mode 100644 src/Http/Http/src/StreamPipeReader.cs delete mode 100644 src/Http/Http/src/StreamPipeReaderOptions.cs delete mode 100644 src/Http/Http/src/StreamPipeWriter.cs delete mode 100644 src/Http/Http/src/ThrowHelper.cs delete mode 100644 src/Http/Http/src/WriteOnlyPipeStream.cs delete mode 100644 src/Http/Http/test/FlushResultCancellationTests.cs delete mode 100644 src/Http/Http/test/PipeStreamTest.cs delete mode 100644 src/Http/Http/test/PipeWriterTests.cs delete mode 100644 src/Http/Http/test/ReadAsyncCancellationTests.cs delete mode 100644 src/Http/Http/test/ReadOnlyPipeStreamTests.cs delete mode 100644 src/Http/Http/test/ReadingAdaptersInteropTests.cs delete mode 100644 src/Http/Http/test/StreamPipeReaderTests.cs delete mode 100644 src/Http/Http/test/StreamPipeTest.cs delete mode 100644 src/Http/Http/test/StreamPipeWriterTests.cs delete mode 100644 src/Http/Http/test/TestMemoryPool.cs delete mode 100644 src/Http/Http/test/WriteOnlyPipeStreamTests.cs delete mode 100644 src/Http/Http/test/WritingAdaptersInteropTests.cs diff --git a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs index 9dd9e01997..819234838c 100644 --- a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs +++ b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs @@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Http { protected HttpRequest() { } public abstract System.IO.Stream Body { get; set; } - public virtual System.IO.Pipelines.PipeReader BodyReader { get { throw null; } set { } } + public virtual System.IO.Pipelines.PipeReader BodyReader { get { throw null; } } public abstract long? ContentLength { get; set; } public abstract string ContentType { get; set; } public abstract Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get; set; } @@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Http { protected HttpResponse() { } public abstract System.IO.Stream Body { get; set; } - public virtual System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } set { } } + public virtual System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } } public abstract long? ContentLength { get; set; } public abstract string ContentType { get; set; } public abstract Microsoft.AspNetCore.Http.IResponseCookies Cookies { get; } diff --git a/src/Http/Http.Abstractions/src/HttpRequest.cs b/src/Http/Http.Abstractions/src/HttpRequest.cs index a63438450b..4a6c58b7e0 100644 --- a/src/Http/Http.Abstractions/src/HttpRequest.cs +++ b/src/Http/Http.Abstractions/src/HttpRequest.cs @@ -105,9 +105,9 @@ namespace Microsoft.AspNetCore.Http public abstract Stream Body { get; set; } /// - /// Gets or sets the request body pipe . + /// Gets the request body pipe . /// - public virtual PipeReader BodyReader { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public virtual PipeReader BodyReader { get => throw new NotImplementedException(); } /// /// Checks the Content-Type header for form types. diff --git a/src/Http/Http.Abstractions/src/HttpResponse.cs b/src/Http/Http.Abstractions/src/HttpResponse.cs index d5a144dd7d..9df4f095e6 100644 --- a/src/Http/Http.Abstractions/src/HttpResponse.cs +++ b/src/Http/Http.Abstractions/src/HttpResponse.cs @@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.Http public abstract Stream Body { get; set; } /// - /// Gets or sets the response body pipe + /// Gets the response body pipe /// - public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); } /// /// Gets or sets the value for the Content-Length response header. diff --git a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs index 70684be914..0591abdce6 100644 --- a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs +++ b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs @@ -3,8 +3,7 @@ using System; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Tests; +using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; @@ -36,15 +35,12 @@ namespace Microsoft.AspNetCore.Http [MemberData(nameof(Encodings))] public async Task WritingTextThatRequiresMultipleSegmentsWorks(Encoding encoding) { - // Need to change the StreamPipeWriter with a capped MemoryPool - var memoryPool = new TestMemoryPool(maxBufferSize: 16); var outputStream = new MemoryStream(); - var streamPipeWriter = new StreamPipeWriter(outputStream, minimumSegmentSize: 0, memoryPool); HttpContext context = new DefaultHttpContext(); - context.Response.BodyWriter = streamPipeWriter; + context.Response.Body = outputStream; - var inputString = "昨日すき焼きを食べました"; + var inputString = string.Concat(Enumerable.Repeat("昨日すき焼きを食べました", 1000)); var expected = encoding.GetBytes(inputString); await context.Response.WriteAsync(inputString, encoding); @@ -52,11 +48,8 @@ namespace Microsoft.AspNetCore.Http var actual = new byte[expected.Length]; var length = outputStream.Read(actual); - var res1 = encoding.GetString(actual); - var res2 = encoding.GetString(expected); Assert.Equal(expected.Length, length); Assert.Equal(expected, actual); - streamPipeWriter.Complete(); } [Theory] diff --git a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs index 8c0f10b5dc..c97e11f219 100644 --- a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs +++ b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs @@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Http.Features } public partial interface IRequestBodyPipeFeature { - System.IO.Pipelines.PipeReader Reader { get; set; } + System.IO.Pipelines.PipeReader Reader { get; } } public partial interface IRequestCookiesFeature { @@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Http.Features } public partial interface IResponseBodyPipeFeature { - System.IO.Pipelines.PipeWriter Writer { get; set; } + System.IO.Pipelines.PipeWriter Writer { get; } } public partial interface IResponseCookiesFeature { diff --git a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs index 9357edb421..99e109b99d 100644 --- a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs +++ b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs @@ -13,6 +13,6 @@ namespace Microsoft.AspNetCore.Http.Features /// /// A representing the request body, if any. /// - PipeReader Reader { get; set; } + PipeReader Reader { get; } } } diff --git a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs index 71a55d12b9..45d0e35f0a 100644 --- a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs +++ b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs @@ -14,6 +14,6 @@ namespace Microsoft.AspNetCore.Http.Features /// /// A representing the response body, if any. /// - PipeWriter Writer { get; set; } + PipeWriter Writer { get; } } } diff --git a/src/Http/Http/perf/NoopStream.cs b/src/Http/Http/perf/NoopStream.cs deleted file mode 100644 index 9dcfe8408f..0000000000 --- a/src/Http/Http/perf/NoopStream.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Http -{ - public class NoopStream : Stream - { - public override bool CanRead => true; - - public override bool CanSeek => throw new NotImplementedException(); - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return Task.FromResult(0); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - return new ValueTask(0); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - } - - public override void Write(byte[] buffer, int offset, int count) - { - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default(CancellationToken)) - { - return default(ValueTask); - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } -} diff --git a/src/Http/Http/perf/StreamPipeReaderBenchmark.cs b/src/Http/Http/perf/StreamPipeReaderBenchmark.cs deleted file mode 100644 index 2b5ef7c24b..0000000000 --- a/src/Http/Http/perf/StreamPipeReaderBenchmark.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO.Pipelines; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; - -namespace Microsoft.AspNetCore.Http -{ - public class StreamPipeReaderBenchmark - { - private StreamPipeReader _pipeReaderNoop; - private StreamPipeReader _pipeReaderHelloWorld; - private StreamPipeReader _pipeReaderHelloWorldAync; - - [IterationSetup] - public void Setup() - { - _pipeReaderNoop = new StreamPipeReader(new NoopStream()); - _pipeReaderHelloWorld = new StreamPipeReader(new HelloWorldStream()); - _pipeReaderHelloWorldAync = new StreamPipeReader(new HelloWorldAsyncStream()); - } - - [Benchmark] - public async Task ReadNoop() - { - await _pipeReaderNoop.ReadAsync(); - } - - [Benchmark] - public async Task ReadHelloWorld() - { - var result = await _pipeReaderHelloWorld.ReadAsync(); - _pipeReaderHelloWorld.AdvanceTo(result.Buffer.End); - } - - [Benchmark] - public async Task ReadHelloWorldAsync() - { - var result = await _pipeReaderHelloWorldAync.ReadAsync(); - _pipeReaderHelloWorldAync.AdvanceTo(result.Buffer.End); - } - - private class HelloWorldStream : NoopStream - { - private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - bytes.CopyTo(buffer, 0); - return Task.FromResult(11); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - bytes.CopyTo(buffer); - - return new ValueTask(11); - } - } - - private class HelloWorldAsyncStream : NoopStream - { - private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await Task.Yield(); - bytes.CopyTo(buffer, 0); - return 11; - } - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - await Task.Yield(); - bytes.CopyTo(buffer); - return 11; - } - } - } -} diff --git a/src/Http/Http/perf/StreamPipeWriterBenchmark.cs b/src/Http/Http/perf/StreamPipeWriterBenchmark.cs deleted file mode 100644 index dd7836120d..0000000000 --- a/src/Http/Http/perf/StreamPipeWriterBenchmark.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.IO.Pipelines; -using System.Text; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; - -namespace Microsoft.AspNetCore.Http -{ - public class StreamPipeWriterBenchmark - { - private Stream _memoryStream; - private StreamPipeWriter _pipeWriter; - private static byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("Hello World"); - private static byte[] _largeWrite = Encoding.ASCII.GetBytes(new string('a', 50000)); - - [IterationSetup] - public void Setup() - { - _memoryStream = new NoopStream(); - _pipeWriter = new StreamPipeWriter(_memoryStream); - } - - [Benchmark] - public async Task WriteHelloWorld() - { - await _pipeWriter.WriteAsync(_helloWorldBytes); - } - - [Benchmark] - public async Task WriteHelloWorldLargeWrite() - { - await _pipeWriter.WriteAsync(_largeWrite); - } - } -} diff --git a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs index 16ad1810c3..2c299f83d9 100644 --- a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs +++ b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs @@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.Http.Features public partial class RequestBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature { public RequestBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { } - public System.IO.Pipelines.PipeReader Reader { get { throw null; } set { } } + public System.IO.Pipelines.PipeReader Reader { get { throw null; } } } public partial class RequestCookiesFeature : Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature { @@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Http.Features public partial class ResponseBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IResponseBodyPipeFeature { public ResponseBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { } - public System.IO.Pipelines.PipeWriter Writer { get { throw null; } set { } } + public System.IO.Pipelines.PipeWriter Writer { get { throw null; } } } public partial class ResponseCookiesFeature : Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature { @@ -326,7 +326,7 @@ namespace Microsoft.AspNetCore.Http.Internal { public DefaultHttpRequest(Microsoft.AspNetCore.Http.DefaultHttpContext context) { } public override System.IO.Stream Body { get { throw null; } set { } } - public override System.IO.Pipelines.PipeReader BodyReader { get { throw null; } set { } } + public override System.IO.Pipelines.PipeReader BodyReader { get { throw null; } } public override long? ContentLength { get { throw null; } set { } } public override string ContentType { get { throw null; } set { } } public override Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get { throw null; } set { } } @@ -353,7 +353,7 @@ namespace Microsoft.AspNetCore.Http.Internal { public DefaultHttpResponse(Microsoft.AspNetCore.Http.DefaultHttpContext context) { } public override System.IO.Stream Body { get { throw null; } set { } } - public override System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } set { } } + public override System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } } public override long? ContentLength { get { throw null; } set { } } public override string ContentType { get { throw null; } set { } } public override Microsoft.AspNetCore.Http.IResponseCookies Cookies { get { throw null; } } @@ -492,93 +492,3 @@ namespace Microsoft.Extensions.DependencyInjection public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } } } -namespace System.IO.Pipelines -{ - public partial class ReadOnlyPipeStream : System.IO.Stream - { - public ReadOnlyPipeStream(System.IO.Pipelines.PipeReader pipeReader) { } - public ReadOnlyPipeStream(System.IO.Pipelines.PipeReader pipeReader, bool allowSynchronousIO) { } - public override bool CanRead { get { throw null; } } - public override bool CanSeek { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public System.IO.Pipelines.PipeReader InnerPipeReader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override int WriteTimeout { get { throw null; } set { } } - public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } - public override int EndRead(System.IAsyncResult asyncResult) { throw null; } - public override void Flush() { } - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - } - public partial class StreamPipeReader : System.IO.Pipelines.PipeReader, System.IDisposable - { - public StreamPipeReader(System.IO.Stream readingStream) { } - public StreamPipeReader(System.IO.Stream readingStream, System.IO.Pipelines.StreamPipeReaderAdapterOptions options) { } - public System.IO.Stream InnerStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override void AdvanceTo(System.SequencePosition consumed) { } - public override void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined) { } - public override void CancelPendingRead() { } - public override void Complete(System.Exception exception = null) { } - public void Dispose() { } - public override void OnWriterCompleted(System.Action callback, object state) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public override System.Threading.Tasks.ValueTask ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override bool TryRead(out System.IO.Pipelines.ReadResult result) { throw null; } - } - public partial class StreamPipeReaderAdapterOptions - { - public const int DefaultMinimumReadThreshold = 256; - public const int DefaultMinimumSegmentSize = 4096; - public static System.IO.Pipelines.StreamPipeReaderAdapterOptions DefaultOptions; - public StreamPipeReaderAdapterOptions() { } - public StreamPipeReaderAdapterOptions(int minimumSegmentSize, int minimumReadThreshold, System.Buffers.MemoryPool memoryPool) { } - public System.Buffers.MemoryPool MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int MinimumReadThreshold { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int MinimumSegmentSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - public partial class StreamPipeWriter : System.IO.Pipelines.PipeWriter, System.IDisposable - { - public StreamPipeWriter(System.IO.Stream writingStream) { } - public StreamPipeWriter(System.IO.Stream writingStream, int minimumSegmentSize, System.Buffers.MemoryPool pool = null) { } - public System.IO.Stream InnerStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override void Advance(int count) { } - public override void CancelPendingFlush() { } - public override void Complete(System.Exception exception = null) { } - public void Dispose() { } - public override System.Threading.Tasks.ValueTask FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override System.Memory GetMemory(int sizeHint = 0) { throw null; } - public override System.Span GetSpan(int sizeHint = 0) { throw null; } - public override void OnReaderCompleted(System.Action callback, object state) { } - } - public partial class WriteOnlyPipeStream : System.IO.Stream - { - public WriteOnlyPipeStream(System.IO.Pipelines.PipeWriter pipeWriter) { } - public WriteOnlyPipeStream(System.IO.Pipelines.PipeWriter pipeWriter, bool allowSynchronousIO) { } - public override bool CanRead { get { throw null; } } - public override bool CanSeek { get { throw null; } } - public override bool CanWrite { get { throw null; } } - public System.IO.Pipelines.PipeWriter InnerPipeWriter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public override long Length { get { throw null; } } - public override long Position { get { throw null; } set { } } - public override int ReadTimeout { get { throw null; } set { } } - public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - public override void EndWrite(System.IAsyncResult asyncResult) { } - public override void Flush() { } - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } - public override int Read(byte[] buffer, int offset, int count) { throw null; } - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - public override void SetLength(long value) { } - public override void Write(byte[] buffer, int offset, int count) { } - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } -} diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index ee86031fde..61154c5f0a 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -2,14 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.IO.Pipelines; +using System.Threading.Tasks; namespace Microsoft.AspNetCore.Http.Features { public class RequestBodyPipeFeature : IRequestBodyPipeFeature { - private StreamPipeReader _internalPipeReader; - private PipeReader _userSetPipeReader; + private PipeReader _internalPipeReader; + private Stream _streamInstanceWhenWrapped; private HttpContext _context; public RequestBodyPipeFeature(HttpContext context) @@ -25,25 +27,21 @@ namespace Microsoft.AspNetCore.Http.Features { get { - if (_userSetPipeReader != null) - { - return _userSetPipeReader; - } - if (_internalPipeReader == null || - !object.ReferenceEquals(_internalPipeReader.InnerStream, _context.Request.Body)) + !ReferenceEquals(_streamInstanceWhenWrapped, _context.Request.Body)) { - _internalPipeReader = new StreamPipeReader(_context.Request.Body); - _context.Response.RegisterForDispose(_internalPipeReader); + _streamInstanceWhenWrapped = _context.Request.Body; + _internalPipeReader = PipeReader.Create(_context.Request.Body); + + _context.Response.OnCompleted((self) => + { + ((PipeReader)self).Complete(); + return Task.CompletedTask; + }, _internalPipeReader); } return _internalPipeReader; } - set - { - _userSetPipeReader = value ?? throw new ArgumentNullException(nameof(value)); - // TODO set the request body Stream to an adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 - } } } } diff --git a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs index 0699679365..2de8c86b9c 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -2,14 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.IO.Pipelines; +using System.Threading.Tasks; namespace Microsoft.AspNetCore.Http.Features { public class ResponseBodyPipeFeature : IResponseBodyPipeFeature { - private StreamPipeWriter _internalPipeWriter; - private PipeWriter _userSetPipeWriter; + private PipeWriter _internalPipeWriter; + private Stream _streamInstanceWhenWrapped; private HttpContext _context; public ResponseBodyPipeFeature(HttpContext context) @@ -25,25 +27,21 @@ namespace Microsoft.AspNetCore.Http.Features { get { - if (_userSetPipeWriter != null) - { - return _userSetPipeWriter; - } - if (_internalPipeWriter == null || - !object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body)) + !ReferenceEquals(_streamInstanceWhenWrapped, _context.Response.Body)) { - _internalPipeWriter = new StreamPipeWriter(_context.Response.Body); - _context.Response.RegisterForDispose(_internalPipeWriter); + _streamInstanceWhenWrapped = _context.Response.Body; + _internalPipeWriter = PipeWriter.Create(_context.Response.Body); + + _context.Response.OnCompleted((self) => + { + ((PipeWriter)self).Complete(); + return Task.CompletedTask; + }, _internalPipeWriter); } return _internalPipeWriter; } - set - { - _userSetPipeWriter = value ?? throw new ArgumentNullException(nameof(value)); - // TODO set the response body Stream to an adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 - } } } } diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs index 964a478510..bc0a4c42dc 100644 --- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs +++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs @@ -174,7 +174,6 @@ namespace Microsoft.AspNetCore.Http.Internal public override PipeReader BodyReader { get { return RequestBodyPipeFeature.Reader; } - set { RequestBodyPipeFeature.Reader = value; } } struct FeatureInterfaces diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs index 068c4ec9bd..19e75c3aa0 100644 --- a/src/Http/Http/src/Internal/DefaultHttpResponse.cs +++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs @@ -112,7 +112,6 @@ namespace Microsoft.AspNetCore.Http.Internal public override PipeWriter BodyWriter { get { return ResponseBodyPipeFeature.Writer; } - set { ResponseBodyPipeFeature.Writer = value; } } public override void OnStarting(Func callback, object state) diff --git a/src/Http/Http/src/ReadOnlyPipeStream.cs b/src/Http/Http/src/ReadOnlyPipeStream.cs deleted file mode 100644 index 919745bc5d..0000000000 --- a/src/Http/Http/src/ReadOnlyPipeStream.cs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO.Pipelines -{ - /// - /// Represents a read-only Stream backed by a PipeReader - /// - public class ReadOnlyPipeStream : Stream - { - private bool _allowSynchronousIO = true; - - /// - /// Creates a new ReadOnlyPipeStream - /// - /// The PipeReader to read from. - public ReadOnlyPipeStream(PipeReader pipeReader) : - this(pipeReader, allowSynchronousIO: true) - { - } - - /// - /// Creates a new ReadOnlyPipeStream - /// - /// The PipeReader to read from. - /// Whether synchronous IO is allowed. - public ReadOnlyPipeStream(PipeReader pipeReader, bool allowSynchronousIO) - { - _allowSynchronousIO = allowSynchronousIO; - InnerPipeReader = pipeReader; - } - - /// - public override bool CanSeek => false; - - /// - public override bool CanRead => true; - - /// - public override bool CanWrite => false; - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - /// - public override int WriteTimeout - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public PipeReader InnerPipeReader { get; } - - /// - public override void Write(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); - - /// - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => throw new NotSupportedException(); - - /// - public override void Flush() - { - throw new NotSupportedException(); - } - - /// - public override Task FlushAsync(CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (!_allowSynchronousIO) - { - ThrowHelper.ThrowInvalidOperationException_SynchronousReadsDisallowed(); - } - return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); - } - - /// - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - var task = ReadAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; - } - - /// - public override int EndRead(IAsyncResult asyncResult) - { - return ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = ReadAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(task2.Result); - } - }, tcs, cancellationToken); - return tcs.Task; - } - - /// - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); - } - - /// - public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) - { - return ReadAsyncInternal(destination, cancellationToken); - } - - private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) - { - while (true) - { - var result = await InnerPipeReader.ReadAsync(cancellationToken); - - if (result.IsCanceled) - { - ThrowHelper.ThrowOperationCanceledException_ReadCanceled(); - } - - var readableBuffer = result.Buffer; - var readableBufferLength = readableBuffer.Length; - - var consumed = readableBuffer.End; - var actual = 0; - try - { - if (readableBufferLength != 0) - { - actual = (int)Math.Min(readableBufferLength, buffer.Length); - - var slice = actual == readableBufferLength ? readableBuffer : readableBuffer.Slice(0, actual); - consumed = slice.End; - slice.CopyTo(buffer.Span); - - return actual; - } - - if (result.IsCompleted) - { - return 0; - } - } - finally - { - InnerPipeReader.AdvanceTo(consumed); - } - } - } - - /// - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - if (destination == null) - { - throw new ArgumentNullException(nameof(destination)); - } - - if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - } - - return CopyToAsyncInternal(destination, cancellationToken); - } - - private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken) - { - while (true) - { - var result = await InnerPipeReader.ReadAsync(cancellationToken); - var readableBuffer = result.Buffer; - var readableBufferLength = readableBuffer.Length; - - try - { - if (readableBufferLength != 0) - { - foreach (var memory in readableBuffer) - { - await destination.WriteAsync(memory, cancellationToken); - } - } - - if (result.IsCompleted) - { - return; - } - } - finally - { - InnerPipeReader.AdvanceTo(readableBuffer.End); - } - } - } - } -} diff --git a/src/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs deleted file mode 100644 index 16e3e0ac32..0000000000 --- a/src/Http/Http/src/StreamPipeReader.cs +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO.Pipelines -{ - /// - /// Implements PipeReader using an underlying stream. - /// - public class StreamPipeReader : PipeReader, IDisposable - { - private readonly int _bufferSize; - private readonly int _minimumReadThreshold; - private readonly MemoryPool _pool; - - private CancellationTokenSource _internalTokenSource; - private bool _isReaderCompleted; - private bool _isStreamCompleted; - private ExceptionDispatchInfo _exceptionInfo; - - private BufferSegment _readHead; - private int _readIndex; - - private BufferSegment _readTail; - private long _bufferedBytes; - private bool _examinedEverything; - private object _lock = new object(); - - /// - /// Creates a new StreamPipeReader. - /// - /// The stream to read from. - public StreamPipeReader(Stream readingStream) - : this(readingStream, StreamPipeReaderAdapterOptions.DefaultOptions) - { - } - - /// - /// Creates a new StreamPipeReader. - /// - /// The stream to read from. - /// The options to use. - public StreamPipeReader(Stream readingStream, StreamPipeReaderAdapterOptions options) - { - InnerStream = readingStream ?? throw new ArgumentNullException(nameof(readingStream)); - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (options.MinimumReadThreshold <= 0) - { - throw new ArgumentOutOfRangeException(nameof(options.MinimumReadThreshold)); - } - - _minimumReadThreshold = Math.Min(options.MinimumReadThreshold, options.MinimumSegmentSize); - _pool = options.MemoryPool == MemoryPool.Shared ? null : options.MemoryPool; - _bufferSize = _pool == null ? options.MinimumSegmentSize : Math.Min(options.MinimumSegmentSize, _pool.MaxBufferSize); - } - - /// - /// Gets the inner stream that is being read from. - /// - public Stream InnerStream { get; } - - /// - public override void AdvanceTo(SequencePosition consumed) - { - AdvanceTo(consumed, consumed); - } - - private CancellationTokenSource InternalTokenSource - { - get - { - lock (_lock) - { - if (_internalTokenSource == null) - { - _internalTokenSource = new CancellationTokenSource(); - } - return _internalTokenSource; - } - } - set - { - _internalTokenSource = value; - } - } - - /// - public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) - { - ThrowIfCompleted(); - - if (_readHead == null || _readTail == null) - { - ThrowHelper.ThrowInvalidOperationException_NoDataRead(); - } - - AdvanceTo((BufferSegment)consumed.GetObject(), consumed.GetInteger(), (BufferSegment)examined.GetObject(), examined.GetInteger()); - } - - private void AdvanceTo(BufferSegment consumedSegment, int consumedIndex, BufferSegment examinedSegment, int examinedIndex) - { - if (consumedSegment == null) - { - return; - } - - var returnStart = _readHead; - var returnEnd = consumedSegment; - - var consumedBytes = new ReadOnlySequence(returnStart, _readIndex, consumedSegment, consumedIndex).Length; - - _bufferedBytes -= consumedBytes; - - Debug.Assert(_bufferedBytes >= 0); - - _examinedEverything = false; - - if (examinedSegment == _readTail) - { - // If we examined everything, we force ReadAsync to actually read from the underlying stream - // instead of returning a ReadResult from TryRead. - _examinedEverything = examinedIndex == _readTail.End; - } - - // Three cases here: - // 1. All data is consumed. If so, we reset _readHead and _readTail to _readTail's original memory owner - // SetMemory on a IMemoryOwner will reset the internal Memory to be an empty segment - // 2. A segment is entirely consumed but there is still more data in nextSegments - // We are allowed to remove an extra segment. by setting returnEnd to be the next block. - // 3. We are in the middle of a segment. - // Move _readHead and _readIndex to consumedSegment and index - if (_bufferedBytes == 0) - { - _readTail.SetMemory(_readTail.MemoryOwner); - _readHead = _readTail; - returnEnd = _readTail; - _readIndex = 0; - } - else if (consumedIndex == returnEnd.Length) - { - var nextBlock = returnEnd.NextSegment; - _readHead = nextBlock; - _readIndex = 0; - returnEnd = nextBlock; - } - else - { - _readHead = consumedSegment; - _readIndex = consumedIndex; - } - - // Remove all blocks that are freed (except the last one) - while (returnStart != returnEnd) - { - returnStart.ResetMemory(); - returnStart = returnStart.NextSegment; - } - } - - /// - public override void CancelPendingRead() - { - InternalTokenSource.Cancel(); - } - - /// - public override void Complete(Exception exception = null) - { - if (_isReaderCompleted) - { - return; - } - - _isReaderCompleted = true; - if (exception != null) - { - _exceptionInfo = ExceptionDispatchInfo.Capture(exception); - } - - var segment = _readHead; - while (segment != null) - { - segment.ResetMemory(); - segment = segment.NextSegment; - } - } - - /// - public override void OnWriterCompleted(Action callback, object state) - { - throw new NotSupportedException("OnWriterCompleted is not supported"); - } - - /// - public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) - { - // TODO ReadyAsync needs to throw if there are overlapping reads. - ThrowIfCompleted(); - - // PERF: store InternalTokenSource locally to avoid querying it twice (which acquires a lock) - var tokenSource = InternalTokenSource; - if (TryReadInternal(tokenSource, out var readResult)) - { - return readResult; - } - - if (_isStreamCompleted) - { - return new ReadResult(buffer: default, isCanceled: false, isCompleted: true); - } - - var reg = new CancellationTokenRegistration(); - if (cancellationToken.CanBeCanceled) - { - reg = cancellationToken.Register(state => ((StreamPipeReader)state).Cancel(), this); - } - - using (reg) - { - var isCanceled = false; - try - { - AllocateReadTail(); -#if NETCOREAPP3_0 - var length = await InnerStream.ReadAsync(_readTail.AvailableMemory.Slice(_readTail.End), tokenSource.Token); -#elif NETSTANDARD2_0 - if (!MemoryMarshal.TryGetArray(_readTail.AvailableMemory.Slice(_readTail.End), out var arraySegment)) - { - ThrowHelper.CreateInvalidOperationException_NoArrayFromMemory(); - } - - var length = await _readingStream.ReadAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count, tokenSource.Token); -#else -#error Target frameworks need to be updated. -#endif - Debug.Assert(length + _readTail.End <= _readTail.AvailableMemory.Length); - - _readTail.End += length; - _bufferedBytes += length; - - if (length == 0) - { - _isStreamCompleted = true; - } - } - catch (OperationCanceledException) - { - ClearCancellationToken(); - - if (cancellationToken.IsCancellationRequested) - { - throw; - } - - isCanceled = true; - } - - return new ReadResult(GetCurrentReadOnlySequence(), isCanceled, IsCompletedOrThrow()); - } - } - - private void ClearCancellationToken() - { - lock (_lock) - { - _internalTokenSource = null; - } - } - - private void ThrowIfCompleted() - { - if (_isReaderCompleted) - { - ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed(); - } - } - - public override bool TryRead(out ReadResult result) - { - ThrowIfCompleted(); - - return TryReadInternal(InternalTokenSource, out result); - } - - private bool TryReadInternal(CancellationTokenSource source, out ReadResult result) - { - var isCancellationRequested = source.IsCancellationRequested; - if (isCancellationRequested || _bufferedBytes > 0 && (!_examinedEverything || _isStreamCompleted)) - { - // If TryRead/ReadAsync are called and cancellation is requested, we need to make sure memory is allocated for the ReadResult, - // otherwise if someone calls advance afterward on the ReadResult, it will throw. - if (isCancellationRequested) - { - AllocateReadTail(); - - ClearCancellationToken(); - } - - result = new ReadResult( - GetCurrentReadOnlySequence(), - isCanceled: isCancellationRequested, - IsCompletedOrThrow()); - return true; - } - - result = new ReadResult(); - return false; - } - - private ReadOnlySequence GetCurrentReadOnlySequence() - { - return new ReadOnlySequence(_readHead, _readIndex, _readTail, _readTail.End); - } - - private void AllocateReadTail() - { - if (_readHead == null) - { - Debug.Assert(_readTail == null); - _readHead = AllocateSegment(); - _readTail = _readHead; - } - else if (_readTail.WritableBytes < _minimumReadThreshold) - { - CreateNewTailSegment(); - } - } - - private void CreateNewTailSegment() - { - BufferSegment nextSegment = AllocateSegment(); - _readTail.SetNext(nextSegment); - _readTail = nextSegment; - } - - private BufferSegment AllocateSegment() - { - var nextSegment = new BufferSegment(); - - if (_pool is null) - { - nextSegment.SetMemory(ArrayPool.Shared.Rent(_bufferSize)); - } - else - { - nextSegment.SetMemory(_pool.Rent(_bufferSize)); - } - - return nextSegment; - } - - private void Cancel() - { - InternalTokenSource.Cancel(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsCompletedOrThrow() - { - if (!_isStreamCompleted) - { - return false; - } - if (_exceptionInfo != null) - { - ThrowLatchedException(); - } - return true; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void ThrowLatchedException() - { - _exceptionInfo.Throw(); - } - - public void Dispose() - { - Complete(); - } - } -} diff --git a/src/Http/Http/src/StreamPipeReaderOptions.cs b/src/Http/Http/src/StreamPipeReaderOptions.cs deleted file mode 100644 index 2a932e0c39..0000000000 --- a/src/Http/Http/src/StreamPipeReaderOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Text; - -namespace System.IO.Pipelines -{ - public class StreamPipeReaderAdapterOptions - { - public static StreamPipeReaderAdapterOptions DefaultOptions = new StreamPipeReaderAdapterOptions(); - public const int DefaultMinimumSegmentSize = 4096; - public const int DefaultMinimumReadThreshold = 256; - - public StreamPipeReaderAdapterOptions() - { - } - - public StreamPipeReaderAdapterOptions(int minimumSegmentSize, int minimumReadThreshold, MemoryPool memoryPool) - { - MinimumSegmentSize = minimumSegmentSize; - MinimumReadThreshold = minimumReadThreshold; - MemoryPool = memoryPool; - } - - public int MinimumSegmentSize { get; set; } = DefaultMinimumSegmentSize; - - public int MinimumReadThreshold { get; set; } = DefaultMinimumReadThreshold; - - public MemoryPool MemoryPool { get; set; } = MemoryPool.Shared; - } -} diff --git a/src/Http/Http/src/StreamPipeWriter.cs b/src/Http/Http/src/StreamPipeWriter.cs deleted file mode 100644 index 087a2b53b6..0000000000 --- a/src/Http/Http/src/StreamPipeWriter.cs +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO.Pipelines -{ - /// - /// Implements PipeWriter using a underlying stream. - /// - public class StreamPipeWriter : PipeWriter, IDisposable - { - private readonly int _minimumSegmentSize; - private int _bytesWritten; - - private List _completedSegments; - private Memory _currentSegment; - private object _currentSegmentOwner; - private MemoryPool _pool; - private int _position; - - private CancellationTokenSource _internalTokenSource; - private bool _isCompleted; - private object _lockObject = new object(); - - private CancellationTokenSource InternalTokenSource - { - get - { - lock (_lockObject) - { - if (_internalTokenSource == null) - { - _internalTokenSource = new CancellationTokenSource(); - } - return _internalTokenSource; - } - } - } - - /// - /// Creates a new StreamPipeWrapper - /// - /// The stream to write to - public StreamPipeWriter(Stream writingStream) : this(writingStream, 4096) - { - } - - public StreamPipeWriter(Stream writingStream, int minimumSegmentSize, MemoryPool pool = null) - { - _minimumSegmentSize = minimumSegmentSize; - InnerStream = writingStream; - _pool = pool == MemoryPool.Shared ? null : pool; - } - - /// - /// Gets the inner stream that is being written to. - /// - public Stream InnerStream { get; } - - /// - public override void Advance(int count) - { - if (_currentSegment.IsEmpty) // TODO confirm this - { - throw new InvalidOperationException("No writing operation. Make sure GetMemory() was called."); - } - - if (count >= 0) - { - if (_currentSegment.Length < _position + count) - { - throw new InvalidOperationException("Can't advance past buffer size."); - } - _bytesWritten += count; - _position += count; - } - } - - /// - public override Memory GetMemory(int sizeHint = 0) - { - if (_isCompleted) - { - ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed(); - } - if (sizeHint < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(sizeHint)); - } - - EnsureCapacity(sizeHint); - - return _currentSegment.Slice(_position); - } - - /// - public override Span GetSpan(int sizeHint = 0) - { - if (_isCompleted) - { - ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed(); - } - if (sizeHint < 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(sizeHint)); - } - - EnsureCapacity(sizeHint); - - return _currentSegment.Span.Slice(_position); - } - - /// - public override void CancelPendingFlush() - { - Cancel(); - } - - /// - public override void Complete(Exception exception = null) - { - if (_isCompleted) - { - return; - } - - Dispose(); - // We still want to cleanup segments before throwing an exception. - if (_bytesWritten > 0 && exception == null) - { - ThrowHelper.ThrowInvalidOperationException_DataNotAllFlushed(); - } - } - - /// - public override void OnReaderCompleted(Action callback, object state) - { - throw new NotSupportedException("OnReaderCompleted isn't supported in StreamPipeWrapper."); - } - - /// - public override ValueTask FlushAsync(CancellationToken cancellationToken = default) - { - if (_bytesWritten == 0) - { - return new ValueTask(new FlushResult(isCanceled: false, _isCompleted)); - } - - return FlushAsyncInternal(cancellationToken); - } - - private void Cancel() - { - InternalTokenSource.Cancel(); - } - - private async ValueTask FlushAsyncInternal(CancellationToken cancellationToken = default) - { - // Write all completed segments and whatever remains in the current segment - // and flush the result. - var reg = new CancellationTokenRegistration(); - if (cancellationToken.CanBeCanceled) - { - reg = cancellationToken.Register(state => ((StreamPipeWriter)state).Cancel(), this); - } - using (reg) - { - var localToken = InternalTokenSource.Token; - try - { - if (_completedSegments != null && _completedSegments.Count > 0) - { - var count = _completedSegments.Count; - for (var i = 0; i < count; i++) - { - var segment = _completedSegments[0]; -#if NETCOREAPP3_0 - await InnerStream.WriteAsync(segment.Buffer.Slice(0, segment.Length), localToken); -#elif NETSTANDARD2_0 - MemoryMarshal.TryGetArray(segment.Buffer, out var arraySegment); - await InnerStream.WriteAsync(arraySegment.Array, 0, segment.Length, localToken); -#else -#error Target frameworks need to be updated. -#endif - _bytesWritten -= segment.Length; - segment.Return(); - _completedSegments.RemoveAt(0); - } - } - - if (!_currentSegment.IsEmpty) - { -#if NETCOREAPP3_0 - await InnerStream.WriteAsync(_currentSegment.Slice(0, _position), localToken); -#elif NETSTANDARD2_0 - MemoryMarshal.TryGetArray(_currentSegment, out var arraySegment); - await InnerStream.WriteAsync(arraySegment.Array, 0, _position, localToken); -#else -#error Target frameworks need to be updated. -#endif - _bytesWritten -= _position; - _position = 0; - } - - await InnerStream.FlushAsync(localToken); - - return new FlushResult(isCanceled: false, _isCompleted); - } - catch (OperationCanceledException) - { - // Remove the cancellation token such that the next time Flush is called - // A new CTS is created. - lock (_lockObject) - { - _internalTokenSource = null; - } - - if (cancellationToken.IsCancellationRequested) - { - throw; - } - - // Catch any cancellation and translate it into setting isCanceled = true - return new FlushResult(isCanceled: true, _isCompleted); - } - } - } - - private void EnsureCapacity(int sizeHint) - { - // This does the Right Thing. It only subtracts _position from the current segment length if it's non-null. - // If _currentSegment is null, it returns 0. - var remainingSize = _currentSegment.Length - _position; - - // If the sizeHint is 0, any capacity will do - // Otherwise, the buffer must have enough space for the entire size hint, or we need to add a segment. - if ((sizeHint == 0 && remainingSize > 0) || (sizeHint > 0 && remainingSize >= sizeHint)) - { - // We have capacity in the current segment - return; - } - - AddSegment(sizeHint); - } - - private void AddSegment(int sizeHint = 0) - { - if (_currentSegment.Length != 0) - { - // We're adding a segment to the list - if (_completedSegments == null) - { - _completedSegments = new List(); - } - - // Position might be less than the segment length if there wasn't enough space to satisfy the sizeHint when - // GetMemory was called. In that case we'll take the current segment and call it "completed", but need to - // ignore any empty space in it. - _completedSegments.Add(new CompletedBuffer(_currentSegmentOwner, _currentSegment, _position)); - } - - if (_pool is null) - { - _currentSegment = ArrayPool.Shared.Rent(Math.Max(sizeHint, _minimumSegmentSize)); - _currentSegmentOwner = _currentSegment; - } - else if (sizeHint <= _pool.MaxBufferSize) - { - // Get a new buffer using the minimum segment size, unless the size hint is larger than a single segment. - // Also, the size cannot be larger than the MaxBufferSize of the MemoryPool - var owner = _pool.Rent(Math.Clamp(sizeHint, _minimumSegmentSize, _pool.MaxBufferSize)); - _currentSegment = owner.Memory; - _currentSegmentOwner = owner; - } - else - { - _currentSegment = new byte[sizeHint]; - } - - _position = 0; - } - - public void Dispose() - { - if (_isCompleted) - { - return; - } - - _isCompleted = true; - - _internalTokenSource?.Dispose(); - - if (_completedSegments != null) - { - foreach (var segment in _completedSegments) - { - segment.Return(); - } - } - - DisposeOwner(_currentSegmentOwner); - } - - private static void DisposeOwner(object owner) - { - if (owner is IMemoryOwner memoryOwner) - { - memoryOwner.Dispose(); - } - else if (owner is byte[] array) - { - ArrayPool.Shared.Return(array); - } - } - - /// - /// Holds a byte[] from the pool and a size value. Basically a Memory but guaranteed to be backed by an ArrayPool byte[], so that we know we can return it. - /// - private readonly struct CompletedBuffer - { - private readonly object _memoryOwner; - - public Memory Buffer { get; } - public int Length { get; } - - public ReadOnlySpan Span => Buffer.Span; - - public CompletedBuffer(object owner, Memory buffer, int length) - { - _memoryOwner = owner; - - Buffer = buffer; - Length = length; - } - - public void Return() - { - DisposeOwner(_memoryOwner); - } - } - } -} diff --git a/src/Http/Http/src/ThrowHelper.cs b/src/Http/Http/src/ThrowHelper.cs deleted file mode 100644 index 0745b9bf65..0000000000 --- a/src/Http/Http/src/ThrowHelper.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -namespace System.IO.Pipelines -{ - internal static class ThrowHelper - { - public static void ThrowInvalidOperationException_NoReadingAllowed() => throw CreateInvalidOperationException_NoReadingAllowed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoReadingAllowed() => new InvalidOperationException("Reading is not allowed after reader was completed."); - - public static void ThrowInvalidOperationException_NoArrayFromMemory() => throw CreateInvalidOperationException_NoArrayFromMemory(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoArrayFromMemory() => new InvalidOperationException("Could not get byte[] from Memory."); - - public static void ThrowInvalidOperationException_NoDataRead() => throw CreateInvalidOperationException_NoDataRead(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoDataRead() => new InvalidOperationException("No data has been read into the StreamPipeReader."); - - public static void ThrowInvalidOperationException_SynchronousReadsDisallowed() => throw CreateInvalidOperationException_SynchronousReadsDisallowed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_SynchronousReadsDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set allowSynchronousIO to true instead."); - - public static void ThrowInvalidOperationException_SynchronousWritesDisallowed() => throw CreateInvalidOperationException_SynchronousWritesDisallowed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_SynchronousWritesDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set allowSynchronousIO to true instead."); - - public static void ThrowInvalidOperationException_SynchronousFlushesDisallowed() => throw CreateInvalidOperationException_SynchronousFlushesDisallowed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_SynchronousFlushesDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call FlushAsync or set allowSynchronousIO to true instead."); - - public static void ThrowInvalidOperationException_DataNotAllFlushed() => throw CreateInvalidOperationException_DataNotAllFlushed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_DataNotAllFlushed() => new InvalidOperationException("Complete called without flushing the StreamPipeWriter. Call FlushAsync() before calling Complete()."); - - public static void ThrowInvalidOperationException_NoWritingAllowed() => throw CreateInvalidOperationException_NoWritingAllowed(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoWritingAllowed() => new InvalidOperationException("Writing is not allowed after writer was completed."); - - public static void ThrowArgumentOutOfRangeException(string argument) => throw CreateArgumentOutOfRangeException(argument); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateArgumentOutOfRangeException(string argument) => new ArgumentOutOfRangeException(argument); - - public static void ThrowOperationCanceledException_ReadCanceled() => throw CreateOperationCanceledException_ReadCanceled(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateOperationCanceledException_ReadCanceled() => new OperationCanceledException("Read was canceled on underlying PipeReader."); - } -} diff --git a/src/Http/Http/src/WriteOnlyPipeStream.cs b/src/Http/Http/src/WriteOnlyPipeStream.cs deleted file mode 100644 index 1d8d23328b..0000000000 --- a/src/Http/Http/src/WriteOnlyPipeStream.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Internal; - -namespace System.IO.Pipelines -{ - /// - /// Represents a WriteOnlyStream backed by a PipeWriter - /// - public class WriteOnlyPipeStream : Stream - { - private bool _allowSynchronousIO = true; - - /// - /// Creates a new WriteOnlyStream - /// - /// The PipeWriter to write to. - public WriteOnlyPipeStream(PipeWriter pipeWriter) : - this(pipeWriter, allowSynchronousIO: true) - { - } - - /// - /// Creates a new WriteOnlyStream - /// - /// The PipeWriter to write to. - /// Whether synchronous IO is allowed. - public WriteOnlyPipeStream(PipeWriter pipeWriter, bool allowSynchronousIO) - { - InnerPipeWriter = pipeWriter; - _allowSynchronousIO = allowSynchronousIO; - } - - /// - public override bool CanSeek => false; - - /// - public override bool CanRead => false; - - /// - public override bool CanWrite => true; - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - /// - public override int ReadTimeout - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public PipeWriter InnerPipeWriter { get; } - - /// - public override int Read(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); - - /// - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => throw new NotSupportedException(); - - /// - public override void Flush() - { - if (!_allowSynchronousIO) - { - ThrowHelper.ThrowInvalidOperationException_SynchronousFlushesDisallowed(); - } - - FlushAsync(default).GetAwaiter().GetResult(); - } - - /// - public override Task FlushAsync(CancellationToken cancellationToken) - { - return InnerPipeWriter.FlushAsync(cancellationToken).GetAsTask(); - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - if (!_allowSynchronousIO) - { - ThrowHelper.ThrowInvalidOperationException_SynchronousWritesDisallowed(); - } - WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult(); - } - - /// - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - var task = WriteAsync(buffer, offset, count, default, state); - if (callback != null) - { - task.ContinueWith(t => callback.Invoke(t)); - } - return task; - } - - /// - public override void EndWrite(IAsyncResult asyncResult) - { - ((Task)asyncResult).GetAwaiter().GetResult(); - } - - private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) - { - var tcs = new TaskCompletionSource(state); - var task = WriteAsync(buffer, offset, count, cancellationToken); - task.ContinueWith((task2, state2) => - { - var tcs2 = (TaskCompletionSource)state2; - if (task2.IsCanceled) - { - tcs2.SetCanceled(); - } - else if (task2.IsFaulted) - { - tcs2.SetException(task2.Exception); - } - else - { - tcs2.SetResult(null); - } - }, tcs, cancellationToken); - return tcs.Task; - } - - /// - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), cancellationToken); - } - - /// - public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) - { - return new ValueTask(WriteAsyncInternal(source, cancellationToken)); - } - - private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken = default) - { - return InnerPipeWriter.WriteAsync(source, cancellationToken).GetAsTask(); - } - } -} diff --git a/src/Http/Http/test/Features/FormFeatureTests.cs b/src/Http/Http/test/Features/FormFeatureTests.cs index 4dff8b8daf..f57ed454b7 100644 --- a/src/Http/Http/test/Features/FormFeatureTests.cs +++ b/src/Http/Http/test/Features/FormFeatureTests.cs @@ -98,7 +98,9 @@ namespace Microsoft.AspNetCore.Http.Features await pipe.Writer.WriteAsync(formContent); pipe.Writer.Complete(); - context.Request.BodyReader = pipe.Reader; + var mockFeature = new MockRequestBodyPipeFeature(); + mockFeature.Reader = pipe.Reader; + context.Features.Set(mockFeature); IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest }); context.Features.Set(formFeature); @@ -108,16 +110,21 @@ namespace Microsoft.AspNetCore.Http.Features Assert.Equal("bar", formCollection["foo"]); Assert.Equal("2", formCollection["baz"]); - // Cached + // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formFeature.Form, formCollection); - // Cleanup + // Cleanup await responseFeature.CompleteAsync(); } + private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature + { + public PipeReader Reader { get; set; } + } + private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T"; private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\""; diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index 83ccd7e928..27d74ac8b7 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Buffers; using System.IO; using System.IO.Pipelines; @@ -24,78 +23,7 @@ namespace Microsoft.AspNetCore.Http.Features var pipeBody = feature.Reader; - Assert.True(pipeBody is StreamPipeReader); - Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); - } - - [Fact] - public async Task RequestBodyReadCanWorkWithPipe() - { - var expectedString = "abcdef"; - var feature = InitializeFeatureWithData(expectedString); - - var data = await feature.Reader.ReadAsync(); - Assert.Equal(expectedString, GetStringFromReadResult(data)); - } - - [Fact] - public void RequestBodySetPipeReaderReturnsSameValue() - { - var context = new DefaultHttpContext(); - - var feature = new RequestBodyPipeFeature(context); - - var pipeReader = new Pipe().Reader; - feature.Reader = pipeReader; - - Assert.Equal(pipeReader, feature.Reader); - } - - [Fact] - public void RequestBodySetPipeReadReturnsUserSetValueAlways() - { - var context = new DefaultHttpContext(); - - var feature = new RequestBodyPipeFeature(context); - - var expectedPipeReader = new Pipe().Reader; - feature.Reader = expectedPipeReader; - - // Because the user set the RequestBodyPipe, this will return the user set pipeReader - context.Request.Body = new MemoryStream(); - - Assert.Equal(expectedPipeReader, feature.Reader); - } - - [Fact] - public async Task RequestBodyDoesNotAffectUserSetPipe() - { - var expectedString = "abcdef"; - var feature = InitializeFeatureWithData("hahaha"); - feature.Reader = await GetPipeReaderWithData(expectedString); - - var data = await feature.Reader.ReadAsync(); - Assert.Equal(expectedString, GetStringFromReadResult(data)); - } - - [Fact] - public void RequestBodyGetPipeReaderAfterSettingBodyTwice() - { - var context = new DefaultHttpContext(); - - context.Request.Body = new MemoryStream(); - - var feature = new RequestBodyPipeFeature(context); - - var pipeBody = feature.Reader; - - // Requery the PipeReader after setting the body again. - var expectedStream = new MemoryStream(); - context.Request.Body = expectedStream; - pipeBody = feature.Reader; - - Assert.True(pipeBody is StreamPipeReader); - Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); + Assert.NotNull(pipeBody); } [Fact] @@ -112,23 +40,9 @@ namespace Microsoft.AspNetCore.Http.Features Assert.Equal(expectedString, GetStringFromReadResult(data)); } - private RequestBodyPipeFeature InitializeFeatureWithData(string input) - { - var context = new DefaultHttpContext(); - context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes(input)); - return new RequestBodyPipeFeature(context); - } - private static string GetStringFromReadResult(ReadResult data) { return Encoding.ASCII.GetString(data.Buffer.ToArray()); } - - private async Task GetPipeReaderWithData(string input) - { - var pipe = new Pipe(); - await pipe.Writer.WriteAsync(Encoding.ASCII.GetBytes(input)); - return pipe.Reader; - } } } diff --git a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs index d2f8d67f8b..7dc69080e0 100644 --- a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.IO.Pipelines; using Xunit; namespace Microsoft.AspNetCore.Http.Features @@ -20,37 +19,7 @@ namespace Microsoft.AspNetCore.Http.Features var pipeBody = feature.Writer; - Assert.True(pipeBody is StreamPipeWriter); - Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); - } - - [Fact] - public void ResponseBodySetPipeReaderReturnsSameValue() - { - var context = new DefaultHttpContext(); - var feature = new ResponseBodyPipeFeature(context); - - var pipeWriter = new Pipe().Writer; - feature.Writer = pipeWriter; - - Assert.Equal(pipeWriter, feature.Writer); - } - - [Fact] - public void ResponseBodyGetPipeWriterAfterSettingBodyTwice() - { - var context = new DefaultHttpContext(); - var expectedStream = new MemoryStream(); - context.Response.Body = new MemoryStream(); - - var feature = new ResponseBodyPipeFeature(context); - - var pipeBody = feature.Writer; - context.Response.Body = expectedStream; - pipeBody = feature.Writer; - - Assert.True(pipeBody is StreamPipeWriter); - Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); + Assert.NotNull(pipeBody); } } } diff --git a/src/Http/Http/test/FlushResultCancellationTests.cs b/src/Http/Http/test/FlushResultCancellationTests.cs deleted file mode 100644 index 86dfac17c2..0000000000 --- a/src/Http/Http/test/FlushResultCancellationTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO.Pipelines; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class FlushResultCancellationTests : StreamPipeTest - { - [Fact] - public async Task FlushAsyncWithNewCancellationTokenNotAffectedByPrevious() - { - var cancellationTokenSource1 = new CancellationTokenSource(); - PipeWriter buffer = Writer.WriteEmpty(10); - await buffer.FlushAsync(cancellationTokenSource1.Token); - - cancellationTokenSource1.Cancel(); - - var cancellationTokenSource2 = new CancellationTokenSource(); - buffer = Writer.WriteEmpty(10); - - await buffer.FlushAsync(cancellationTokenSource2.Token); - } - } -} diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs index 11b2bd62b1..ead7012fac 100644 --- a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs @@ -251,36 +251,6 @@ namespace Microsoft.AspNetCore.Http.Internal Assert.NotNull(bodyPipe); } - [Fact] - public void BodyReader_CanSet() - { - var pipeReader = new Pipe().Reader; - var context = new DefaultHttpContext(); - - context.Request.BodyReader = pipeReader; - - Assert.Equal(pipeReader, context.Request.BodyReader); - } - - [Fact] - public void BodyReader_WrapsStream() - { - var context = new DefaultHttpContext(); - var expectedStream = new MemoryStream(); - context.Request.Body = expectedStream; - - var bodyPipe = context.Request.BodyReader as StreamPipeReader; - - Assert.Equal(expectedStream, bodyPipe.InnerStream); - } - - [Fact] - public void BodyReader_ThrowsWhenSettingNull() - { - var context = new DefaultHttpContext(); - Assert.Throws(() => context.Request.BodyReader = null); - } - private class CustomRouteValuesFeature : IRouteValuesFeature { public RouteValueDictionary RouteValues { get; set; } diff --git a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs index e071817754..ccc4540587 100644 --- a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs @@ -73,35 +73,6 @@ namespace Microsoft.AspNetCore.Http.Internal Assert.NotNull(bodyPipe); } - [Fact] - public void BodyWriter_CanSet() - { - var response = new DefaultHttpContext(); - var pipeWriter = new Pipe().Writer; - response.Response.BodyWriter = pipeWriter; - - Assert.Equal(pipeWriter, response.Response.BodyWriter); - } - - [Fact] - public void BodyWriter_WrapsStream() - { - var context = new DefaultHttpContext(); - var expectedStream = new MemoryStream(); - context.Response.Body = expectedStream; - - var bodyPipe = context.Response.BodyWriter as StreamPipeWriter; - - Assert.Equal(expectedStream, bodyPipe.InnerStream); - } - - [Fact] - public void BodyWriter_ThrowsWhenSettingNull() - { - var context = new DefaultHttpContext(); - Assert.Throws(() => context.Response.BodyWriter = null); - } - [Fact] public async Task ResponseStart_CallsFeatureIfSet() { diff --git a/src/Http/Http/test/PipeStreamTest.cs b/src/Http/Http/test/PipeStreamTest.cs deleted file mode 100644 index 94b3ded7be..0000000000 --- a/src/Http/Http/test/PipeStreamTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Text; -using System.Threading.Tasks; - -namespace System.IO.Pipelines.Tests -{ - public class PipeStreamTest : IDisposable - { - public Stream ReadingStream { get; set; } - public Stream WritingStream { get; set; } - - public Pipe Pipe { get; set; } - - public PipeReader Reader => Pipe.Reader; - - public PipeWriter Writer => Pipe.Writer; - - public PipeStreamTest() - { - Pipe = new Pipe(); - ReadingStream = new ReadOnlyPipeStream(Reader); - WritingStream = new WriteOnlyPipeStream(Writer); - } - - public void Dispose() - { - Writer.Complete(); - Reader.Complete(); - } - - public async Task WriteStringToStreamAsync(string input) - { - await WritingStream.WriteAsync(Encoding.ASCII.GetBytes(input)); - } - - public async Task WriteStringToPipeAsync(string input) - { - await Writer.WriteAsync(Encoding.ASCII.GetBytes(input)); - } - - public async Task WriteByteArrayToPipeAsync(byte[] input) - { - await Writer.WriteAsync(input); - } - - public async Task ReadFromPipeAsStringAsync() - { - var readResult = await Reader.ReadAsync(); - var result = Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Reader.AdvanceTo(readResult.Buffer.End); - return result; - } - - public async Task ReadFromStreamAsStringAsync() - { - var memory = new Memory(new byte[4096]); - var readLength = await ReadingStream.ReadAsync(memory); - var result = Encoding.ASCII.GetString(memory.ToArray(), 0, readLength); - return result; - } - - public async Task ReadFromPipeAsByteArrayAsync() - { - var readResult = await Reader.ReadAsync(); - var result = readResult.Buffer.ToArray(); - Reader.AdvanceTo(readResult.Buffer.End); - return result; - } - - public Task ReadFromStreamAsByteArrayAsync(int size) - { - return ReadFromStreamAsByteArrayAsync(size, ReadingStream); - } - - public async Task ReadFromStreamAsByteArrayAsync(int size, Stream stream) - { - var memory = new Memory(new byte[size]); - var readLength = await stream.ReadAsync(memory); - return memory.Slice(0, readLength).ToArray(); - } - } -} diff --git a/src/Http/Http/test/PipeWriterTests.cs b/src/Http/Http/test/PipeWriterTests.cs deleted file mode 100644 index 714cbbbccb..0000000000 --- a/src/Http/Http/test/PipeWriterTests.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.IO.Pipelines; -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class PipeWriterTests : StreamPipeTest - { - - [Theory] - [InlineData(3, -1, 0)] - [InlineData(3, 0, -1)] - [InlineData(3, 0, 4)] - [InlineData(3, 4, 0)] - [InlineData(3, -1, -1)] - [InlineData(3, 4, 4)] - public void ThrowsForInvalidParameters(int arrayLength, int offset, int length) - { - var array = new byte[arrayLength]; - for (var i = 0; i < array.Length; i++) - { - array[i] = (byte)(i + 1); - } - - Writer.Write(new Span(array, 0, 0)); - Writer.Write(new Span(array, array.Length, 0)); - - try - { - Writer.Write(new Span(array, offset, length)); - Assert.True(false); - } - catch (Exception ex) - { - Assert.True(ex is ArgumentOutOfRangeException); - } - - Writer.Write(new Span(array, 0, array.Length)); - Assert.Equal(array, Read()); - } - - [Theory] - [InlineData(0, 3)] - [InlineData(1, 2)] - [InlineData(2, 1)] - [InlineData(1, 1)] - public void CanWriteWithOffsetAndLength(int offset, int length) - { - var array = new byte[] { 1, 2, 3 }; - - Writer.Write(new Span(array, offset, length)); - - Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read()); - } - - [Fact] - public void CanWriteIntoHeadlessBuffer() - { - - Writer.Write(new byte[] { 1, 2, 3 }); - Assert.Equal(new byte[] { 1, 2, 3 }, Read()); - } - - [Fact] - public void CanGetNewMemoryWhenSizeTooLarge() - { - var memory = Writer.GetMemory(0); - - var memoryLarge = Writer.GetMemory(10000); - - Assert.NotEqual(memory, memoryLarge); - } - - [Fact] - public void CanGetSameMemoryWhenNoAdvance() - { - var memory = Writer.GetMemory(0); - - var secondMemory = Writer.GetMemory(0); - - Assert.Equal(memory, secondMemory); - } - - [Theory] - [InlineData(0)] - [InlineData(2048)] - public void GetSpanWithZeroSizeHintReturnsMaxBufferSizeOfPool(int sizeHint) - { - var span = Writer.GetSpan(sizeHint); - - Assert.Equal(4096, span.Length); - } - - [Fact] - public void CanGetSameSpanWhenNoAdvance() - { - var span = Writer.GetSpan(0); - - var secondSpan = Writer.GetSpan(0); - - Assert.True(span.SequenceEqual(secondSpan)); - } - - [Theory] - [InlineData(16, 32, 32)] - [InlineData(16, 16, 16)] - [InlineData(64, 32, 64)] - [InlineData(40, 32, 64)] // memory sizes are powers of 2. - public void CheckMinimumSegmentSizeWithGetMemory(int minimumSegmentSize, int getMemorySize, int expectedSize) - { - var writer = new StreamPipeWriter(new MemoryStream(), minimumSegmentSize); - var memory = writer.GetMemory(getMemorySize); - - Assert.Equal(expectedSize, memory.Length); - } - - [Fact] - public void CanWriteMultipleTimes() - { - - Writer.Write(new byte[] { 1 }); - Writer.Write(new byte[] { 2 }); - Writer.Write(new byte[] { 3 }); - - Assert.Equal(new byte[] { 1, 2, 3 }, Read()); - } - - [Fact] - public void CanWriteOverTheBlockLength() - { - Memory memory = Writer.GetMemory(); - - IEnumerable source = Enumerable.Range(0, memory.Length).Select(i => (byte)i); - byte[] expectedBytes = source.Concat(source).Concat(source).ToArray(); - - Writer.Write(expectedBytes); - - Assert.Equal(expectedBytes, Read()); - } - - [Fact] - public void EnsureAllocatesSpan() - { - var span = Writer.GetSpan(10); - - Assert.True(span.Length >= 10); - // 0 byte Flush would not complete the reader so we complete. - Writer.Complete(); - Assert.Equal(new byte[] { }, Read()); - } - - [Fact] - public void SlicesSpanAndAdvancesAfterWrite() - { - int initialLength = Writer.GetSpan(3).Length; - - - Writer.Write(new byte[] { 1, 2, 3 }); - Span span = Writer.GetSpan(); - - Assert.Equal(initialLength - 3, span.Length); - Assert.Equal(new byte[] { 1, 2, 3 }, Read()); - } - - [Theory] - [InlineData(5)] - [InlineData(50)] - [InlineData(500)] - [InlineData(5000)] - [InlineData(50000)] - public async Task WriteLargeDataBinary(int length) - { - var data = new byte[length]; - new Random(length).NextBytes(data); - PipeWriter output = Writer; - output.Write(data); - await output.FlushAsync(); - - var input = Read(); - Assert.Equal(data, input.ToArray()); - } - - [Fact] - public async Task CanWriteNothingToBuffer() - { - Writer.GetMemory(0); - Writer.Advance(0); // doing nothing, the hard way - await Writer.FlushAsync(); - } - - [Fact] - public void EmptyWriteDoesNotThrow() - { - Writer.Write(new byte[0]); - } - - [Fact] - public void ThrowsOnAdvanceOverMemorySize() - { - Memory buffer = Writer.GetMemory(1); - var exception = Assert.Throws(() => Writer.Advance(buffer.Length + 1)); - Assert.Equal("Can't advance past buffer size.", exception.Message); - } - - [Fact] - public void ThrowsOnAdvanceWithNoMemory() - { - PipeWriter buffer = Writer; - var exception = Assert.Throws(() => buffer.Advance(1)); - Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message); - } - } -} diff --git a/src/Http/Http/test/ReadAsyncCancellationTests.cs b/src/Http/Http/test/ReadAsyncCancellationTests.cs deleted file mode 100644 index 84d183c512..0000000000 --- a/src/Http/Http/test/ReadAsyncCancellationTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.IO.Pipelines; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class ReadAsyncCancellationTests : StreamPipeTest - { - [Fact] - public async Task AdvanceShouldResetStateIfReadCanceled() - { - Reader.CancelPendingRead(); - - var result = await Reader.ReadAsync(); - var buffer = result.Buffer; - Reader.AdvanceTo(buffer.End); - - Assert.False(result.IsCompleted); - Assert.True(result.IsCanceled); - Assert.True(buffer.IsEmpty); - } - - [Fact] - public async Task CancellingBeforeAdvance() - { - Write(Encoding.ASCII.GetBytes("Hello World")); - - var result = await Reader.ReadAsync(); - var buffer = result.Buffer; - - Assert.Equal(11, buffer.Length); - Assert.False(result.IsCanceled); - Assert.True(buffer.IsSingleSegment); - var array = new byte[11]; - buffer.First.Span.CopyTo(array); - Assert.Equal("Hello World", Encoding.ASCII.GetString(array)); - - Reader.CancelPendingRead(); - - Reader.AdvanceTo(buffer.End); - - var awaitable = Reader.ReadAsync(); - - Assert.True(awaitable.IsCompleted); - - result = await awaitable; - - Assert.True(result.IsCanceled); - - Reader.AdvanceTo(result.Buffer.Start, result.Buffer.Start); - } - - [Fact] - public async Task ReadAsyncWithNewCancellationTokenNotAffectedByPrevious() - { - Write(new byte[1]); - - var cancellationTokenSource1 = new CancellationTokenSource(); - var result = await Reader.ReadAsync(cancellationTokenSource1.Token); - Reader.AdvanceTo(result.Buffer.Start); - - cancellationTokenSource1.Cancel(); - var cancellationTokenSource2 = new CancellationTokenSource(); - - // Verifying that ReadAsync does not throw - result = await Reader.ReadAsync(cancellationTokenSource2.Token); - Reader.AdvanceTo(result.Buffer.Start); - } - - [Fact] - public async Task CancellingPendingReadBeforeReadAsync() - { - Reader.CancelPendingRead(); - - ReadResult result = await Reader.ReadAsync(); - ReadOnlySequence buffer = result.Buffer; - Reader.AdvanceTo(buffer.End); - - Assert.False(result.IsCompleted); - Assert.True(result.IsCanceled); - Assert.True(buffer.IsEmpty); - - byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); - Write(bytes); - - result = await Reader.ReadAsync(); - buffer = result.Buffer; - - Assert.Equal(11, buffer.Length); - Assert.False(result.IsCanceled); - Assert.True(buffer.IsSingleSegment); - var array = new byte[11]; - buffer.First.Span.CopyTo(array); - Assert.Equal("Hello World", Encoding.ASCII.GetString(array)); - - Reader.AdvanceTo(buffer.Start, buffer.Start); - } - - [Fact] - public void ReadAsyncCompletedAfterPreCancellation() - { - Reader.CancelPendingRead(); - Write(new byte[] { 1, 2, 3 }); - - ValueTaskAwaiter awaitable = Reader.ReadAsync().GetAwaiter(); - - Assert.True(awaitable.IsCompleted); - - ReadResult result = awaitable.GetResult(); - - Assert.True(result.IsCanceled); - - awaitable = Reader.ReadAsync().GetAwaiter(); - - Assert.True(awaitable.IsCompleted); - - Reader.AdvanceTo(awaitable.GetResult().Buffer.End); - } - } -} diff --git a/src/Http/Http/test/ReadOnlyPipeStreamTests.cs b/src/Http/Http/test/ReadOnlyPipeStreamTests.cs deleted file mode 100644 index f88a935670..0000000000 --- a/src/Http/Http/test/ReadOnlyPipeStreamTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Threading; -using System.Threading.Tasks; -using Moq; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class ReadOnlyPipeStreamTests : PipeStreamTest - { - [Fact] - public void CanSeekFalse() - { - Assert.False(ReadingStream.CanSeek); - } - - [Fact] - public void CanReadTrue() - { - Assert.True(ReadingStream.CanRead); - } - - [Fact] - public void CanWriteFalse() - { - Assert.False(ReadingStream.CanWrite); - } - - [Fact] - public void LengthThrows() - { - Assert.Throws(() => ReadingStream.Length); - } - - [Fact] - public void PositionThrows() - { - Assert.Throws(() => ReadingStream.Position); - Assert.Throws(() => ReadingStream.Position = 1); - } - - [Fact] - public void SeekThrows() - { - Assert.Throws(() => ReadingStream.Seek(0, SeekOrigin.Begin)); - } - - [Fact] - public void SetLengthThrows() - { - Assert.Throws(() => ReadingStream.SetLength(1)); - } - - [Fact] - public void WriteThrows() - { - Assert.Throws(() => ReadingStream.Write(new byte[1], 0, 1)); - } - - [Fact] - public async Task WriteAsyncThrows() - { - await Assert.ThrowsAsync(async () => await ReadingStream.WriteAsync(new byte[1], 0, 1)); - } - - [Fact] - public void ReadTimeoutThrows() - { - Assert.Throws(() => ReadingStream.WriteTimeout = 1); - Assert.Throws(() => ReadingStream.WriteTimeout); - } - - [Fact] - public async Task ReadAsyncWorks() - { - var expected = "Hello World!"; - - await WriteStringToPipeAsync(expected); - - Assert.Equal(expected, await ReadFromStreamAsStringAsync()); - } - - [Fact] - public async Task BasicLargeRead() - { - var expected = new byte[8000]; - - await WriteByteArrayToPipeAsync(expected); - - Assert.Equal(expected, await ReadFromStreamAsByteArrayAsync(8000)); - } - - [Fact] - public async Task ReadAsyncIsCalledFromCallingRead() - { - var pipeReader = await SetupMockPipeReader(); - var stream = new ReadOnlyPipeStream(pipeReader.Object); - - stream.Read(new byte[1]); - - pipeReader.Verify(m => m.ReadAsync(It.IsAny())); - } - - [Fact] - public async Task ReadAsyncIsCalledFromCallingReadAsync() - { - var pipeReader = await SetupMockPipeReader(); - var stream = new ReadOnlyPipeStream(pipeReader.Object); - - await stream.ReadAsync(new byte[1]); - - pipeReader.Verify(m => m.ReadAsync(It.IsAny())); - } - - [Fact] - public async Task ReadAsyncCancellationTokenIsPassedIntoReadAsync() - { - var pipeReader = await SetupMockPipeReader(); - var stream = new ReadOnlyPipeStream(pipeReader.Object); - var token = new CancellationToken(); - - await stream.ReadAsync(new byte[1], token); - - pipeReader.Verify(m => m.ReadAsync(token)); - } - - [Fact] - public async Task CopyToAsyncWorks() - { - const int expectedSize = 8000; - var expected = new byte[expectedSize]; - - await WriteByteArrayToPipeAsync(expected); - - Writer.Complete(); - var destStream = new MemoryStream(); - - await ReadingStream.CopyToAsync(destStream); - - Assert.Equal(expectedSize, destStream.Length); - } - - [Fact] - public void BlockSyncIOThrows() - { - var readOnlyPipeStream = new ReadOnlyPipeStream(Reader, allowSynchronousIO: false); - Assert.Throws(() => readOnlyPipeStream.Read(new byte[0], 0, 0)); - } - - [Fact] - public void InnerPipeReaderReturnsPipeReader() - { - var readOnlyPipeStream = new ReadOnlyPipeStream(Reader, allowSynchronousIO: false); - Assert.Equal(Reader, readOnlyPipeStream.InnerPipeReader); - } - - [Fact] - public async Task ThrowsOperationCanceledExceptionIfCancelPendingReadWasCalledOnInnerPipeReader() - { - var readOnlyPipeStream = new ReadOnlyPipeStream(Reader); - var readOperation = readOnlyPipeStream.ReadAsync(new byte[1]); - - Assert.False(readOperation.IsCompleted); - - Reader.CancelPendingRead(); - - var ex = await Assert.ThrowsAsync(async () => await readOperation); - - Assert.Equal(ThrowHelper.CreateOperationCanceledException_ReadCanceled().Message, ex.Message); - } - - private async Task> SetupMockPipeReader() - { - await WriteByteArrayToPipeAsync(new byte[1]); - - var pipeReader = new Mock(); - pipeReader - .Setup(m => m.ReadAsync(It.IsAny())) - .Returns(new ValueTask(new ReadResult(new ReadOnlySequence(new byte[1]), false, false))); - return pipeReader; - } - } -} diff --git a/src/Http/Http/test/ReadingAdaptersInteropTests.cs b/src/Http/Http/test/ReadingAdaptersInteropTests.cs deleted file mode 100644 index fad1909b13..0000000000 --- a/src/Http/Http/test/ReadingAdaptersInteropTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class ReadingAdaptersInteropTests - { - [Fact] - public async Task CheckBasicReadPipeApi() - { - var pipe = new Pipe(); - var readStream = new ReadOnlyPipeStream(pipe.Reader); - var pipeReader = new StreamPipeReader(readStream); - - await pipe.Writer.WriteAsync(new byte[10]); - var res = await pipeReader.ReadAsync(); - Assert.Equal(new byte[10], res.Buffer.ToArray()); - } - - [Fact] - public async Task CheckNestedPipeApi() - { - var pipe = new Pipe(); - var reader = pipe.Reader; - for (var i = 0; i < 3; i++) - { - var readStream = new ReadOnlyPipeStream(reader); - reader = new StreamPipeReader(readStream); - } - - await pipe.Writer.WriteAsync(new byte[10]); - var res = await reader.ReadAsync(); - Assert.Equal(new byte[10], res.Buffer.ToArray()); - } - - [Fact] - public async Task CheckBasicReadStreamApi() - { - var stream = new MemoryStream(); - await stream.WriteAsync(new byte[10]); - stream.Position = 0; - - var pipeReader = new StreamPipeReader(stream); - var readOnlyStream = new ReadOnlyPipeStream(pipeReader); - - var resSize = await readOnlyStream.ReadAsync(new byte[10]); - - Assert.Equal(10, resSize); - } - - [Fact] - public async Task CheckNestedStreamApi() - { - var stream = new MemoryStream(); - await stream.WriteAsync(new byte[10]); - stream.Position = 0; - - Stream readOnlyStream = stream; - for (var i = 0; i < 3; i++) - { - var pipeReader = new StreamPipeReader(readOnlyStream); - readOnlyStream = new ReadOnlyPipeStream(pipeReader); - } - - var resSize = await readOnlyStream.ReadAsync(new byte[10]); - - Assert.Equal(10, resSize); - } - - [Fact] - public async Task ReadsCanBeCanceledViaProvidedCancellationToken() - { - var readOnlyStream = new ReadOnlyPipeStream(new HangingPipeReader()); - var pipeReader = new StreamPipeReader(readOnlyStream); - - var cts = new CancellationTokenSource(1); - await Task.Delay(1); - await Assert.ThrowsAsync(async () => await pipeReader.ReadAsync(cts.Token)); - } - - [Fact] - public async Task ReadCanBeCancelledViaCancelPendingReadWhenReadIsAsync() - { - var readOnlyStream = new ReadOnlyPipeStream(new HangingPipeReader()); - var pipeReader = new StreamPipeReader(readOnlyStream); - - var result = new ReadResult(); - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var task = Task.Run(async () => - { - var readingTask = pipeReader.ReadAsync(); - tcs.SetResult(0); - result = await readingTask; - }); - await tcs.Task; - pipeReader.CancelPendingRead(); - await task; - - Assert.True(result.IsCanceled); - } - - private class HangingPipeReader : PipeReader - { - public override void AdvanceTo(SequencePosition consumed) - { - throw new NotImplementedException(); - } - - public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) - { - throw new NotImplementedException(); - } - - public override void CancelPendingRead() - { - throw new NotImplementedException(); - } - - public override void Complete(Exception exception = null) - { - throw new NotImplementedException(); - } - - public override void OnWriterCompleted(Action callback, object state) - { - throw new NotImplementedException(); - } - - public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) - { - await Task.Delay(30000, cancellationToken); - return new ReadResult(); - } - - public override bool TryRead(out ReadResult result) - { - result = new ReadResult(); - return false; - } - } - } -} diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs deleted file mode 100644 index bf76875001..0000000000 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ /dev/null @@ -1,752 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public partial class StreamPipeReaderTests : StreamPipeTest - { - [Fact] - public async Task CanRead() - { - Write(Encoding.ASCII.GetBytes("Hello World")); - var readResult = await Reader.ReadAsync(); - var buffer = readResult.Buffer; - - Assert.Equal(11, buffer.Length); - Assert.True(buffer.IsSingleSegment); - var array = new byte[11]; - buffer.First.Span.CopyTo(array); - Assert.Equal("Hello World", Encoding.ASCII.GetString(array)); - Reader.AdvanceTo(buffer.End); - } - - [Fact] - public async Task CanReadMultipleTimes() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - var readResult = await Reader.ReadAsync(); - - Assert.Equal(MinimumSegmentSize, readResult.Buffer.Length); - Assert.True(readResult.Buffer.IsSingleSegment); - - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - readResult = await Reader.ReadAsync(); - Assert.Equal(MinimumSegmentSize * 2, readResult.Buffer.Length); - Assert.False(readResult.Buffer.IsSingleSegment); - - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - readResult = await Reader.ReadAsync(); - Assert.Equal(10000, readResult.Buffer.Length); - Assert.False(readResult.Buffer.IsSingleSegment); - - Reader.AdvanceTo(readResult.Buffer.End); - } - - [Fact] - public async Task ReadWithAdvance() - { - WriteByteArray(9000); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.End); - - readResult = await Reader.ReadAsync(); - Assert.Equal(MinimumSegmentSize, readResult.Buffer.Length); - Assert.True(readResult.Buffer.IsSingleSegment); - } - - [Fact] - public async Task ReadWithAdvanceDifferentSegmentSize() - { - CreateReader(minimumSegmentSize: 4095); - - WriteByteArray(9000); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.End); - - readResult = await Reader.ReadAsync(); - Assert.Equal(4095, readResult.Buffer.Length); - Assert.True(readResult.Buffer.IsSingleSegment); - } - - [Fact] - public async Task ReadWithAdvanceSmallSegments() - { - CreateReader(); - - WriteByteArray(128); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.End); - - readResult = await Reader.ReadAsync(); - Assert.Equal(16, readResult.Buffer.Length); - Assert.True(readResult.Buffer.IsSingleSegment); - } - - [Fact] - public async Task ReadConsumePartialReadAsyncCallsTryRead() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.GetPosition(2048)); - - // Confirm readResults are the same. - var readResult2 = await Reader.ReadAsync(); - - var didRead = Reader.TryRead(out var readResult3); - - Assert.Equal(readResult2, readResult3); - } - - [Fact] - public async Task ReadConsumeEntireTryReadReturnsNothing() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.End); - var didRead = Reader.TryRead(out readResult); - - Assert.False(didRead); - } - - [Fact] - public async Task ReadExaminePartialReadAsyncDoesNotReturnMoreBytes() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.GetPosition(2048)); - - var readResult2 = await Reader.ReadAsync(); - - Assert.Equal(readResult, readResult2); - } - - [Fact] - public async Task ReadExamineEntireReadAsyncReturnsNewData() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - var readResult2 = await Reader.ReadAsync(); - Assert.NotEqual(readResult, readResult2); - } - - [Fact] - public async Task ReadCanBeCancelledViaProvidedCancellationToken() - { - var pipeReader = new StreamPipeReader(new HangingStream()); - var cts = new CancellationTokenSource(1); - await Task.Delay(1); - await Assert.ThrowsAsync(async () => await pipeReader.ReadAsync(cts.Token)); - } - - [Fact] - public async Task ReadCanBeCanceledViaCancelPendingReadWhenReadIsAsync() - { - var pipeReader = new StreamPipeReader(new HangingStream()); - - var result = new ReadResult(); - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var task = Task.Run(async () => - { - var readingTask = pipeReader.ReadAsync(); - tcs.SetResult(0); - result = await readingTask; - }); - await tcs.Task; - pipeReader.CancelPendingRead(); - await task; - - Assert.True(result.IsCanceled); - } - - [Fact] - public async Task ReadAsyncReturnsCanceledIfCanceledBeforeRead() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - // Make sure state isn't used from before - for (var i = 0; i < 3; i++) - { - Reader.CancelPendingRead(); - var readResultTask = Reader.ReadAsync(); - Assert.True(readResultTask.IsCompleted); - var readResult = readResultTask.GetAwaiter().GetResult(); - Assert.True(readResult.IsCanceled); - readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.End); - } - } - - [Fact] - public async Task ReadAsyncReturnsCanceledInterleaved() - { - Write(new byte[10000]); - - // Cancel and Read interleaved to confirm cancellations are independent - for (var i = 0; i < 3; i++) - { - Reader.CancelPendingRead(); - var readResultTask = Reader.ReadAsync(); - Assert.True(readResultTask.IsCompleted); - var readResult = readResultTask.GetAwaiter().GetResult(); - Assert.True(readResult.IsCanceled); - - readResult = await Reader.ReadAsync(); - Assert.False(readResult.IsCanceled); - } - } - - [Fact] - public async Task AdvanceWithEmptySequencePositionNoop() - { - Write(Encoding.ASCII.GetBytes(new string('a', 10000))); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start); - var readResult2 = await Reader.ReadAsync(); - - Assert.Equal(readResult, readResult2); - } - - [Fact] - public async Task AdvanceToInvalidCursorThrows() - { - Write(new byte[100]); - - var result = await Reader.ReadAsync(); - var buffer = result.Buffer; - - Reader.AdvanceTo(buffer.End); - - Reader.CancelPendingRead(); - result = await Reader.ReadAsync(); - Assert.Throws(() => Reader.AdvanceTo(buffer.End)); - Reader.AdvanceTo(result.Buffer.End); - } - - [Fact] - public void AdvanceWithoutReadingWithValidSequencePosition() - { - var sequencePosition = new SequencePosition(new BufferSegment(), 5); - Assert.Throws(() => Reader.AdvanceTo(sequencePosition)); - } - - [Fact] - public async Task AdvanceMultipleSegments() - { - CreateReader(); - - WriteByteArray(128); - - var result = await Reader.ReadAsync(); - Assert.Equal(16, result.Buffer.Length); - Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - - var result2 = await Reader.ReadAsync(); - Assert.Equal(32, result2.Buffer.Length); - Reader.AdvanceTo(result.Buffer.End, result2.Buffer.End); - - var result3 = await Reader.ReadAsync(); - Assert.Equal(32, result3.Buffer.Length); - } - - [Fact] - public async Task AdvanceMultipleSegmentsEdgeCase() - { - CreateReader(); - - WriteByteArray(128); - - var result = await Reader.ReadAsync(); - Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - result = await Reader.ReadAsync(); - Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - - var result2 = await Reader.ReadAsync(); - Assert.Equal(48, result2.Buffer.Length); - Reader.AdvanceTo(result.Buffer.End, result2.Buffer.End); - - var result3 = await Reader.ReadAsync(); - Assert.Equal(32, result3.Buffer.Length); - } - - [Fact] - public async Task CompleteReaderWithoutAdvanceDoesNotThrow() - { - WriteByteArray(100); - await Reader.ReadAsync(); - Reader.Complete(); - } - - [Fact] - public async Task AdvanceAfterCompleteThrows() - { - WriteByteArray(100); - var buffer = (await Reader.ReadAsync()).Buffer; - - Reader.Complete(); - - var exception = Assert.Throws(() => Reader.AdvanceTo(buffer.End)); - Assert.Equal("Reading is not allowed after reader was completed.", exception.Message); - } - - [Fact] - public async Task ReadBetweenBlocks() - { - var blockSize = 16; - CreateReader(); - - WriteWithoutPosition(Enumerable.Repeat((byte)'a', blockSize - 5).ToArray()); - Write(Encoding.ASCII.GetBytes("Hello World")); - - // ReadAsync will only return one chunk at a time, so Advance/ReadAsync to get two chunks - var result = await Reader.ReadAsync(); - Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - result = await Reader.ReadAsync(); - - var buffer = result.Buffer; - Assert.False(buffer.IsSingleSegment); - var helloBuffer = buffer.Slice(blockSize - 5); - Assert.False(helloBuffer.IsSingleSegment); - var memory = new List>(); - foreach (var m in helloBuffer) - { - memory.Add(m); - } - - var spans = memory; - Reader.AdvanceTo(buffer.Start, buffer.Start); - - Assert.Equal(2, memory.Count); - var helloBytes = new byte[spans[0].Length]; - spans[0].Span.CopyTo(helloBytes); - var worldBytes = new byte[spans[1].Length]; - spans[1].Span.CopyTo(worldBytes); - Assert.Equal("Hello", Encoding.ASCII.GetString(helloBytes)); - Assert.Equal(" World", Encoding.ASCII.GetString(worldBytes)); - } - - [Fact] - public async Task ThrowsOnReadAfterCompleteReader() - { - Reader.Complete(); - - await Assert.ThrowsAsync(async () => await Reader.ReadAsync()); - } - - [Fact] - public void TryReadAfterCancelPendingReadReturnsTrue() - { - Reader.CancelPendingRead(); - - var gotData = Reader.TryRead(out var result); - - Assert.True(result.IsCanceled); - - Reader.AdvanceTo(result.Buffer.End); - } - - [Fact] - public void ReadAsyncWithDataReadyReturnsTaskWithValue() - { - WriteByteArray(20); - var task = Reader.ReadAsync(); - Assert.True(IsTaskWithResult(task)); - } - - [Fact] - public async Task ArrayPoolUsedByDefault() - { - WriteByteArray(20); - var reader = new StreamPipeReader(Stream); - var result = await reader.ReadAsync(); - - SequenceMarshal.TryGetReadOnlySequenceSegment( - result.Buffer, - out var startSegment, - out var startIndex, - out var endSegment, - out var endIndex); - - var start = (BufferSegment)startSegment; - var end = (BufferSegment)endSegment; - - Assert.Same(start, end); - Assert.IsType(start.MemoryOwner); - - reader.AdvanceTo(result.Buffer.End); - reader.Complete(); - } - - [Fact] - public void CancelledReadAsyncReturnsTaskWithValue() - { - Reader.CancelPendingRead(); - var task = Reader.ReadAsync(); - Assert.True(IsTaskWithResult(task)); - } - - [Fact] - public async Task AdvancePastMinReadSizeReadAsyncReturnsMoreData() - { - CreateReader(); - - WriteByteArray(32); - var result = await Reader.ReadAsync(); - Assert.Equal(16, result.Buffer.Length); - - Reader.AdvanceTo(result.Buffer.GetPosition(12), result.Buffer.End); - result = await Reader.ReadAsync(); - Assert.Equal(20, result.Buffer.Length); - } - - [Fact] - public async Task ExamineEverythingResetsAfterSuccessfulRead() - { - WriteByteArray(10000); - - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - var readResult2 = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult2.Buffer.GetPosition(2000)); - - var readResult3 = await Reader.ReadAsync(); - Assert.Equal(6192, readResult3.Buffer.Length); - } - - [Fact] - public async Task ReadMultipleTimesAdvanceFreesAppropriately() - { - var pool = new TestMemoryPool(); - CreateReader(memoryPool: pool); - - WriteByteArray(2000); - - for (var i = 0; i < 99; i++) - { - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - } - - var result = await Reader.ReadAsync(); - Reader.AdvanceTo(result.Buffer.End); - Assert.Equal(1, pool.GetRentCount()); - } - - [Fact] - public async Task AsyncReadWorks() - { - Stream = new AsyncStream(); - CreateReader(); - - WriteByteArray(2000); - - for (var i = 0; i < 99; i++) - { - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - } - - var result = await Reader.ReadAsync(); - Assert.Equal(1600, result.Buffer.Length); - Reader.AdvanceTo(result.Buffer.End); - } - - [Fact] - public async Task ConsumePartialBufferWorks() - { - CreateReader(); - - Write(Encoding.ASCII.GetBytes(new string('a', 8))); - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End); - Stream.Position = 0; - - readResult = await Reader.ReadAsync(); - var resultString = Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Assert.Equal(new string('a', 12), resultString); - Reader.AdvanceTo(readResult.Buffer.End); - } - - [Fact] - public async Task ConsumePartialBufferBetweenMultipleSegmentsWorks() - { - CreateReader(); - - Write(Encoding.ASCII.GetBytes(new string('a', 8))); - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End); - Stream.Position = 0; - - readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - Stream.Position = 0; - - readResult = await Reader.ReadAsync(); - var resultString = Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Assert.Equal(new string('a', 20), resultString); - - Reader.AdvanceTo(readResult.Buffer.End); - } - - [Fact] - public async Task SetMinimumReadThresholdSegmentAdvancesCorrectly() - { - CreateReader(minimumReadThreshold: 8); - - WriteByteArray(9); - var readResult = await Reader.ReadAsync(); - Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - AppendByteArray(9); - readResult = await Reader.ReadAsync(); - - foreach (var segment in readResult.Buffer) - { - Assert.Equal(9, segment.Length); - } - Assert.False(readResult.Buffer.IsSingleSegment); - } - - [Fact] - public void SetMinimumReadThresholdOfZeroThrows() - { - Assert.Throws(() => new StreamPipeReader(Stream, - new StreamPipeReaderAdapterOptions(minimumSegmentSize: 4096, minimumReadThreshold: 0, new TestMemoryPool()))); - } - - [Fact] - public void SetOptionsToNullThrows() - { - Assert.Throws(() => new StreamPipeReader(Stream, null)); - } - - [Fact] - public async Task UseBothStreamAndPipeToReadConfirmSameSize() - { - Write(new byte[8]); - var buffer = new byte[4]; - - Stream.Read(buffer, 0, buffer.Length); - var readResult = await Reader.ReadAsync(); - - Assert.Equal(buffer, readResult.Buffer.ToArray()); - } - - [Fact] - public async Task UseStreamThenPipeToReadNoBytesLost() - { - CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); - - var expectedString = WriteString("abcdef"); - var accumulatedResult = ""; - var buffer = new byte[1]; - - for (var i = 0; i < expectedString.Length / 2; i++) - { - // Read from stream then pipe to guarantee no bytes are lost. - accumulatedResult += ReadFromStreamAsString(buffer); - accumulatedResult += await ReadFromPipeAsString(); - } - - Assert.Equal(expectedString, accumulatedResult); - } - - [Fact] - public async Task UsePipeThenStreamToReadNoBytesLost() - { - CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); - - var expectedString = WriteString("abcdef"); - var accumulatedResult = ""; - var buffer = new byte[1]; - - for (var i = 0; i < expectedString.Length / 2; i++) - { - // Read from pipe then stream to guarantee no bytes are lost. - accumulatedResult += await ReadFromPipeAsString(); - accumulatedResult += ReadFromStreamAsString(buffer); - } - - Assert.Equal(expectedString, accumulatedResult); - } - - [Fact] - public async Task UseBothStreamAndPipeToReadWithoutAdvance_StreamIgnoresAdvance() - { - var buffer = new byte[1]; - CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); - - WriteString("abc"); - ReadFromStreamAsString(buffer); - var readResult = await Reader.ReadAsync(); - - // No Advance - // Next call to Stream.Read will get the next 4 bytes rather than the bytes already read by the pipe - Assert.Equal("c", ReadFromStreamAsString(buffer)); - } - - [Fact] - public async Task ReadAsyncWithNoDataCompletesReader() - { - var readResult = await Reader.ReadAsync(); - - Assert.True(readResult.IsCompleted); - } - - [Fact] - public async Task ReadAsyncWithEmptyDataCompletesStream() - { - WriteByteArray(0); - - var readResult = await Reader.ReadAsync(); - - Assert.True(readResult.IsCompleted); - } - - [Fact] - public async Task ReadAsyncAfterReceivingCompletedReadResultDoesNotThrow() - { - Stream = new ThrowAfterZeroByteReadStream(); - Reader = new StreamPipeReader(Stream); - var readResult = await Reader.ReadAsync(); - - readResult = await Reader.ReadAsync(); - Assert.True(readResult.Buffer.IsEmpty); - Assert.True(readResult.IsCompleted); - } - - [Fact] - public void InnerStreamReturnsStream() - { - Assert.Equal(Stream, ((StreamPipeReader)Reader).InnerStream); - } - - [Fact] - public async Task BufferingDataPastEndOfStreamCanBeReadAgain() - { - var helloBytes = Encoding.ASCII.GetBytes("Hello World"); - Write(helloBytes); - - var readResult = await Reader.ReadAsync(); - var buffer = readResult.Buffer; - Reader.AdvanceTo(buffer.Start, buffer.End); - - // Make sure IsCompleted is true - readResult = await Reader.ReadAsync(); - buffer = readResult.Buffer; - Reader.AdvanceTo(buffer.Start, buffer.End); - Assert.True(readResult.IsCompleted); - - var value = await ReadFromPipeAsString(); - Assert.Equal("Hello World", value); - } - - private async Task ReadFromPipeAsString() - { - var readResult = await Reader.ReadAsync(); - - var result = Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Reader.AdvanceTo(readResult.Buffer.End); - return result; - } - - private string ReadFromStreamAsString(byte[] buffer) - { - var res = Stream.Read(buffer, 0, buffer.Length); - return Encoding.ASCII.GetString(buffer); - } - - private string WriteString(string expectedString) - { - Write(Encoding.ASCII.GetBytes(expectedString)); - return expectedString; - } - - private void CreateReader(int minimumSegmentSize = 16, int minimumReadThreshold = 4, MemoryPool memoryPool = null) - { - Reader = new StreamPipeReader(Stream, - new StreamPipeReaderAdapterOptions( - minimumSegmentSize, - minimumReadThreshold, - memoryPool ?? new TestMemoryPool())); - } - - private bool IsTaskWithResult(ValueTask task) - { - return task == new ValueTask(task.Result); - } - - private void WriteByteArray(int size) - { - Write(new byte[size]); - } - - private void AppendByteArray(int size) - { - Append(new byte[size]); - } - - private class AsyncStream : MemoryStream - { - private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await Task.Yield(); - return await base.ReadAsync(buffer, offset, count, cancellationToken); - } - - // Keeping as this code will eventually be ported to corefx -#if NETCOREAPP3_0 - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return await base.ReadAsync(buffer, cancellationToken); - } -#endif - } - - private class ThrowAfterZeroByteReadStream : MemoryStream - { - private bool _throwOnNextCallToRead; - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return ReadAsync(new Memory(buffer, offset, count)).AsTask(); - } - - public override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) - { - if (_throwOnNextCallToRead) - { - throw new Exception(); - } - var bytes = await base.ReadAsync(destination, cancellationToken); - if (bytes == 0) - { - _throwOnNextCallToRead = true; - } - return bytes; - } - } - } -} diff --git a/src/Http/Http/test/StreamPipeTest.cs b/src/Http/Http/test/StreamPipeTest.cs deleted file mode 100644 index 9c9f1f4b06..0000000000 --- a/src/Http/Http/test/StreamPipeTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text; - -namespace System.IO.Pipelines.Tests -{ - public abstract class StreamPipeTest : IDisposable - { - protected const int MaximumSizeHigh = 65; - - protected const int MinimumSegmentSize = 4096; - - public Stream Stream { get; set; } - - public PipeWriter Writer { get; set; } - - public PipeReader Reader { get; set; } - - public TestMemoryPool Pool { get; set; } - - protected StreamPipeTest() - { - Pool = new TestMemoryPool(); - Stream = new MemoryStream(); - Writer = new StreamPipeWriter(Stream, MinimumSegmentSize, Pool); - Reader = new StreamPipeReader(Stream, new StreamPipeReaderAdapterOptions(MinimumSegmentSize, minimumReadThreshold: 256, Pool)); - } - - public void Dispose() - { - Writer.Complete(); - Reader.Complete(); - Pool.Dispose(); - } - - public byte[] Read() - { - Writer.FlushAsync().GetAwaiter().GetResult(); - return ReadWithoutFlush(); - } - - public string ReadAsString() - { - Writer.FlushAsync().GetAwaiter().GetResult(); - return Encoding.ASCII.GetString(ReadWithoutFlush()); - } - - public void Write(byte[] data) - { - Stream.Write(data, 0, data.Length); - Stream.Position = 0; - } - - public void WriteWithoutPosition(byte[] data) - { - Stream.Write(data, 0, data.Length); - } - - public void Append(byte[] data) - { - var originalPosition = Stream.Position; - Stream.Write(data, 0, data.Length); - Stream.Position = originalPosition; - } - - public byte[] ReadWithoutFlush() - { - Stream.Position = 0; - var buffer = new byte[Stream.Length]; - var result = Stream.Read(buffer, 0, (int)Stream.Length); - return buffer; - } - } -} diff --git a/src/Http/Http/test/StreamPipeWriterTests.cs b/src/Http/Http/test/StreamPipeWriterTests.cs deleted file mode 100644 index 43c8166e76..0000000000 --- a/src/Http/Http/test/StreamPipeWriterTests.cs +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.IO; -using System.IO.Pipelines; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class StreamPipeWriterTests : StreamPipeTest - { - [Fact] - public async Task CanWriteAsyncMultipleTimesIntoSameBlock() - { - await Writer.WriteAsync(new byte[] { 1 }); - await Writer.WriteAsync(new byte[] { 2 }); - await Writer.WriteAsync(new byte[] { 3 }); - - Assert.Equal(new byte[] { 1, 2, 3 }, Read()); - } - - [Theory] - [InlineData(100)] - [InlineData(4000)] - public async Task CanAdvanceWithPartialConsumptionOfFirstSegment(int firstWriteLength) - { - Writer = new StreamPipeWriter(Stream, MinimumSegmentSize, new TestMemoryPool(maxBufferSize: 20000)); - await Writer.WriteAsync(Encoding.ASCII.GetBytes("a")); - - var memory = Writer.GetMemory(firstWriteLength); - Writer.Advance(firstWriteLength); - - memory = Writer.GetMemory(); - Writer.Advance(memory.Length); - - await Writer.FlushAsync(); - - Assert.Equal(firstWriteLength + memory.Length + 1, Read().Length); - } - - [Fact] - public async Task WriteCanBeCancelledViaProvidedCancellationToken() - { - var pipeWriter = new StreamPipeWriter(new HangingStream()); - var cts = new CancellationTokenSource(1); - await Assert.ThrowsAsync(async () => await pipeWriter.WriteAsync(Encoding.ASCII.GetBytes("data"), cts.Token)); - } - - [Fact] - public async Task WriteCanBeCanceledViaCancelPendingFlushWhenFlushIsAsync() - { - var pipeWriter = new StreamPipeWriter(new HangingStream()); - FlushResult flushResult = new FlushResult(); - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var task = Task.Run(async () => - { - try - { - var writingTask = pipeWriter.WriteAsync(Encoding.ASCII.GetBytes("data")); - tcs.SetResult(0); - flushResult = await writingTask; - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - throw ex; - } - }); - - await tcs.Task; - - pipeWriter.CancelPendingFlush(); - - await task; - - Assert.True(flushResult.IsCanceled); - } - - [Fact] - public void FlushAsyncCompletedAfterPreCancellation() - { - PipeWriter writableBuffer = Writer.WriteEmpty(1); - - Writer.CancelPendingFlush(); - - ValueTask flushAsync = writableBuffer.FlushAsync(); - - Assert.True(flushAsync.IsCompleted); - - FlushResult flushResult = flushAsync.GetAwaiter().GetResult(); - - Assert.True(flushResult.IsCanceled); - - flushAsync = writableBuffer.FlushAsync(); - - Assert.True(flushAsync.IsCompleted); - } - - [Fact] - public async Task FlushAsyncReturnsCanceledIfCanceledBeforeFlush() - { - await CheckCanceledFlush(); - } - - [Fact] - public async Task FlushAsyncReturnsCanceledIfCanceledBeforeFlushMultipleTimes() - { - for (var i = 0; i < 10; i++) - { - await CheckCanceledFlush(); - } - } - - [Fact] - public async Task FlushAsyncReturnsCanceledInterleaved() - { - for (var i = 0; i < 5; i++) - { - await CheckCanceledFlush(); - await CheckWriteIsNotCanceled(); - } - } - - [Fact] - public async Task CancelPendingFlushBetweenWritesAllDataIsPreserved() - { - Stream = new SingleWriteStream(); - Writer = new StreamPipeWriter(Stream); - FlushResult flushResult = new FlushResult(); - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var task = Task.Run(async () => - { - try - { - await Writer.WriteAsync(Encoding.ASCII.GetBytes("data")); - - var writingTask = Writer.WriteAsync(Encoding.ASCII.GetBytes(" data")); - tcs.SetResult(0); - flushResult = await writingTask; - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - throw ex; - } - }); - - await tcs.Task; - - Writer.CancelPendingFlush(); - - await task; - - Assert.True(flushResult.IsCanceled); - - await Writer.WriteAsync(Encoding.ASCII.GetBytes(" more data")); - Assert.Equal(Encoding.ASCII.GetBytes("data data more data"), Read()); - } - - [Fact] - public async Task CancelPendingFlushAfterAllWritesAllDataIsPreserved() - { - Stream = new CannotFlushStream(); - Writer = new StreamPipeWriter(Stream); - FlushResult flushResult = new FlushResult(); - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var task = Task.Run(async () => - { - try - { - // Create two Segments - // First one will succeed to write, other one will hang. - var writingTask = Writer.WriteAsync(Encoding.ASCII.GetBytes("data")); - tcs.SetResult(0); - flushResult = await writingTask; - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - throw ex; - } - }); - - await tcs.Task; - - Writer.CancelPendingFlush(); - - await task; - - Assert.True(flushResult.IsCanceled); - } - - [Fact] - public async Task CancelPendingFlushLostOfCancellationsNoDataLost() - { - var writeSize = 16; - var singleWriteStream = new SingleWriteStream(); - Stream = singleWriteStream; - Writer = new StreamPipeWriter(Stream, minimumSegmentSize: writeSize); - - for (var i = 0; i < 10; i++) - { - FlushResult flushResult = new FlushResult(); - var expectedData = Encoding.ASCII.GetBytes(new string('a', writeSize)); - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var task = Task.Run(async () => - { - try - { - // Create two Segments - // First one will succeed to write, other one will hang. - for (var j = 0; j < 2; j++) - { - Writer.Write(expectedData); - } - - var flushTask = Writer.FlushAsync(); - tcs.SetResult(0); - flushResult = await flushTask; - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - throw ex; - } - }); - - await tcs.Task; - - Writer.CancelPendingFlush(); - - await task; - - Assert.True(flushResult.IsCanceled); - } - - // Only half of the data was written because every other flush failed. - Assert.Equal(16 * 10, ReadWithoutFlush().Length); - - // Start allowing all writes to make read succeed. - singleWriteStream.AllowAllWrites = true; - - Assert.Equal(16 * 10 * 2, Read().Length); - } - - [Fact] - public async Task UseBothStreamAndPipeToWrite() - { - await WriteStringToPipeWriter("a"); - WriteStringToStream("c"); - - Assert.Equal("ac", ReadAsString()); - } - - [Fact] - public async Task UsePipeThenStreamToWriteMultipleTimes() - { - var expectedString = "abcdef"; - for (var i = 0; i < expectedString.Length; i++) - { - if (i % 2 == 0) - { - WriteStringToStream(expectedString[i].ToString()); - } - else - { - await WriteStringToPipeWriter(expectedString[i].ToString()); - } - } - - Assert.Equal(expectedString, ReadAsString()); - } - - [Fact] - public async Task UseStreamThenPipeToWriteMultipleTimes() - { - var expectedString = "abcdef"; - for (var i = 0; i < expectedString.Length; i++) - { - if (i % 2 == 0) - { - await WriteStringToPipeWriter(expectedString[i].ToString()); - } - else - { - WriteStringToStream(expectedString[i].ToString()); - } - } - - Assert.Equal(expectedString, ReadAsString()); - } - - [Fact] - public void CallCompleteWithoutFlush_ThrowsInvalidOperationException() - { - var memory = Writer.GetMemory(); - Writer.Advance(memory.Length); - var ex = Assert.Throws(() => Writer.Complete()); - Assert.Equal(ThrowHelper.CreateInvalidOperationException_DataNotAllFlushed().Message, ex.Message); - } - - [Fact] - public void CallCompleteWithoutFlushAndException_DoesNotThrowInvalidOperationException() - { - var memory = Writer.GetMemory(); - Writer.Advance(memory.Length); - Writer.Complete(new Exception()); - } - - [Fact] - public void GetMemorySameAsTheMaxPoolSizeUsesThePool() - { - var memory = Writer.GetMemory(Pool.MaxBufferSize); - - Assert.Equal(Pool.MaxBufferSize, memory.Length); - Assert.Equal(1, Pool.GetRentCount()); - } - - [Fact] - public void GetMemoryBiggerThanPoolSizeAllocatesUnpooledArray() - { - var memory = Writer.GetMemory(Pool.MaxBufferSize + 1); - - Assert.Equal(Pool.MaxBufferSize + 1, memory.Length); - Assert.Equal(0, Pool.GetRentCount()); - } - - [Fact] - public void CallComplete_GetMemoryThrows() - { - Writer.Complete(); - Assert.Throws(() => Writer.GetMemory()); - } - - [Fact] - public void CallComplete_GetSpanThrows() - { - Writer.Complete(); - Assert.Throws(() => Writer.GetSpan()); - } - - [Fact] - public void DisposeDoesNotThrowIfUnflushedData() - { - var streamPipeWriter = new StreamPipeWriter(new MemoryStream()); - streamPipeWriter.Write(new byte[1]); - - streamPipeWriter.Dispose(); - } - - [Fact] - public void CompleteAfterDisposeDoesNotThrowIfUnflushedData() - { - var streamPipeWriter = new StreamPipeWriter(new MemoryStream()); - streamPipeWriter.Write(new byte[1]); - - streamPipeWriter.Dispose(); - streamPipeWriter.Complete(); - } - - [Fact] - public void CallGetMemoryWithNegativeSizeHint_ThrowsArgException() - { - Assert.Throws(() => Writer.GetMemory(-1)); - } - - [Fact] - public void CallGetSpanWithNegativeSizeHint_ThrowsArgException() - { - Assert.Throws(() => Writer.GetSpan(-1)); - } - - [Fact] - public async Task GetMemorySlicesCorrectly() - { - var expectedString = "abcdef"; - var memory = Writer.GetMemory(); - - Encoding.ASCII.GetBytes("abc").CopyTo(memory); - Writer.Advance(3); - memory = Writer.GetMemory(); - Encoding.ASCII.GetBytes("def").CopyTo(memory); - Writer.Advance(3); - - await Writer.FlushAsync(); - Assert.Equal(expectedString, ReadAsString()); - } - - [Fact] - public async Task GetSpanSlicesCorrectly() - { - var expectedString = "abcdef"; - - void NonAsyncMethod() - { - var span = Writer.GetSpan(); - - Encoding.ASCII.GetBytes("abc").CopyTo(span); - Writer.Advance(3); - span = Writer.GetSpan(); - Encoding.ASCII.GetBytes("def").CopyTo(span); - Writer.Advance(3); - } - - NonAsyncMethod(); - - await Writer.FlushAsync(); - Assert.Equal(expectedString, ReadAsString()); - } - - [Fact] - public void InnerStreamReturnsStream() - { - Assert.Equal(Stream, ((StreamPipeWriter)Writer).InnerStream); - } - - private void WriteStringToStream(string input) - { - var buffer = Encoding.ASCII.GetBytes(input); - Stream.Write(buffer, 0, buffer.Length); - } - - private async Task WriteStringToPipeWriter(string input) - { - await Writer.WriteAsync(Encoding.ASCII.GetBytes(input)); - } - - private async Task CheckWriteIsNotCanceled() - { - var flushResult = await Writer.WriteAsync(Encoding.ASCII.GetBytes("data")); - Assert.False(flushResult.IsCanceled); - } - - private async Task CheckCanceledFlush() - { - PipeWriter writableBuffer = Writer.WriteEmpty(MaximumSizeHigh); - - Writer.CancelPendingFlush(); - - ValueTask flushAsync = writableBuffer.FlushAsync(); - - Assert.True(flushAsync.IsCompleted); - FlushResult flushResult = flushAsync.GetAwaiter().GetResult(); - Assert.True(flushResult.IsCanceled); - await writableBuffer.FlushAsync(); - } - } - - internal class HangingStream : MemoryStream - { - public HangingStream() - { - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await Task.Delay(30000, cancellationToken); - } - - public override async Task FlushAsync(CancellationToken cancellationToken) - { - await Task.Delay(30000, cancellationToken); - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await Task.Delay(30000, cancellationToken); - return 0; - } - - // Keeping as this code will eventually be ported to corefx -#if NETCOREAPP3_0 - public override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) - { - await Task.Delay(30000, cancellationToken); - return 0; - } -#endif - } - - internal class SingleWriteStream : MemoryStream - { - private bool _shouldNextWriteFail; - - public bool AllowAllWrites { get; set; } - - // Keeping as this code will eventually be ported to corefx -#if NETCOREAPP3_0 - public override async ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) - { - try - { - if (_shouldNextWriteFail && !AllowAllWrites) - { - await Task.Delay(30000, cancellationToken); - } - else - { - await base.WriteAsync(source, cancellationToken); - } - } - finally - { - _shouldNextWriteFail = !_shouldNextWriteFail; - } - } -#endif - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - try - { - if (_shouldNextWriteFail && !AllowAllWrites) - { - await Task.Delay(30000, cancellationToken); - } - await base.WriteAsync(buffer, offset, count, cancellationToken); - } - finally - { - _shouldNextWriteFail = !_shouldNextWriteFail; - } - } - } - - internal class CannotFlushStream : MemoryStream - { - public override async Task FlushAsync(CancellationToken cancellationToken) - { - await Task.Delay(30000, cancellationToken); - } - } - - internal static class TestWriterExtensions - { - public static PipeWriter WriteEmpty(this PipeWriter Writer, int count) - { - Writer.GetSpan(count).Slice(0, count).Fill(0); - Writer.Advance(count); - return Writer; - } - } -} diff --git a/src/Http/Http/test/TestMemoryPool.cs b/src/Http/Http/test/TestMemoryPool.cs deleted file mode 100644 index 5276c16ad3..0000000000 --- a/src/Http/Http/test/TestMemoryPool.cs +++ /dev/null @@ -1,206 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - -namespace System.IO.Pipelines.Tests -{ - public class TestMemoryPool : MemoryPool - { - private MemoryPool _pool; - private int _maxBufferSize; - private bool _disposed; - private int _rentCount; - - public TestMemoryPool(int maxBufferSize = 4096) - { - _pool = new CustomMemoryPool(); - _maxBufferSize = maxBufferSize; - } - - public override IMemoryOwner Rent(int minBufferSize = -1) - { - CheckDisposed(); - _rentCount++; - return new PooledMemory(_pool.Rent(minBufferSize), this); - } - - public int GetRentCount() - { - return _rentCount; - } - - protected override void Dispose(bool disposing) - { - _disposed = true; - } - - public override int MaxBufferSize => _maxBufferSize; - - internal void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(TestMemoryPool)); - } - } - - private class PooledMemory : MemoryManager - { - private IMemoryOwner _owner; - - private readonly TestMemoryPool _pool; - - private int _referenceCount; - - private bool _returned; - - private string _leaser; - - public PooledMemory(IMemoryOwner owner, TestMemoryPool pool) - { - _owner = owner; - _pool = pool; - _leaser = Environment.StackTrace; - _referenceCount = 1; - } - - ~PooledMemory() - { - Debug.Assert(_returned, "Block being garbage collected instead of returned to pool" + Environment.NewLine + _leaser); - } - - protected override void Dispose(bool disposing) - { - _pool._rentCount--; - _pool.CheckDisposed(); - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - _pool.CheckDisposed(); - Interlocked.Increment(ref _referenceCount); - - if (!MemoryMarshal.TryGetArray(_owner.Memory, out ArraySegment segment)) - { - throw new InvalidOperationException(); - } - - unsafe - { - try - { - if ((uint)elementIndex > (uint)segment.Count) - { - throw new ArgumentOutOfRangeException(nameof(elementIndex)); - } - - GCHandle handle = GCHandle.Alloc(segment.Array, GCHandleType.Pinned); - - return new MemoryHandle(Unsafe.Add(((void*)handle.AddrOfPinnedObject()), elementIndex + segment.Offset), handle, this); - } - catch - { - Unpin(); - throw; - } - } - } - - public override void Unpin() - { - _pool.CheckDisposed(); - - int newRefCount = Interlocked.Decrement(ref _referenceCount); - - if (newRefCount < 0) - throw new InvalidOperationException(); - - if (newRefCount == 0) - { - _returned = true; - } - } - - protected override bool TryGetArray(out ArraySegment segment) - { - _pool.CheckDisposed(); - return MemoryMarshal.TryGetArray(_owner.Memory, out segment); - } - - public override Memory Memory - { - get - { - _pool.CheckDisposed(); - return _owner.Memory; - } - } - - public override Span GetSpan() - { - _pool.CheckDisposed(); - return _owner.Memory.Span; - } - } - - private class CustomMemoryPool : MemoryPool - { - public override int MaxBufferSize => int.MaxValue; - - public override IMemoryOwner Rent(int minimumBufferSize = -1) - { - if (minimumBufferSize == -1) - { - minimumBufferSize = 4096; - } - - return new ArrayMemoryPoolBuffer(minimumBufferSize); - } - - protected override void Dispose(bool disposing) - { - throw new NotImplementedException(); - } - - private sealed class ArrayMemoryPoolBuffer : IMemoryOwner - { - private T[] _array; - - public ArrayMemoryPoolBuffer(int size) - { - _array = new T[size]; - } - - public Memory Memory - { - get - { - T[] array = _array; - if (array == null) - { - throw new ObjectDisposedException(nameof(array)); - } - - return new Memory(array); - } - } - - public void Dispose() - { - T[] array = _array; - if (array != null) - { - _array = null; - } - } - } - } - } -} diff --git a/src/Http/Http/test/WriteOnlyPipeStreamTests.cs b/src/Http/Http/test/WriteOnlyPipeStreamTests.cs deleted file mode 100644 index 4f4bbc19e3..0000000000 --- a/src/Http/Http/test/WriteOnlyPipeStreamTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; -using Moq; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class WriteOnlyPipeStreamTests : PipeStreamTest - { - [Fact] - public void CanSeekFalse() - { - Assert.False(WritingStream.CanSeek); - } - - [Fact] - public void CanReadFalse() - { - Assert.False(WritingStream.CanRead); - } - - [Fact] - public void CanWriteTrue() - { - Assert.True(WritingStream.CanWrite); - } - - [Fact] - public void LengthThrows() - { - Assert.Throws(() => WritingStream.Length); - } - - [Fact] - public void PositionThrows() - { - Assert.Throws(() => WritingStream.Position); - Assert.Throws(() => WritingStream.Position = 1); - } - - [Fact] - public void SeekThrows() - { - Assert.Throws(() => WritingStream.Seek(0, SeekOrigin.Begin)); - } - - [Fact] - public void SetLengthThrows() - { - Assert.Throws(() => WritingStream.SetLength(1)); - } - - [Fact] - public void ReadThrows() - { - Assert.Throws(() => WritingStream.Read(new byte[1], 0, 1)); - } - - [Fact] - public async Task ReadAsyncThrows() - { - await Assert.ThrowsAsync(async () => await WritingStream.ReadAsync(new byte[1], 0, 1)); - } - - [Fact] - public void ReadTimeoutThrows() - { - Assert.Throws(() => WritingStream.ReadTimeout = 1); - Assert.Throws(() => WritingStream.ReadTimeout); - } - - [Fact] - public async Task WriteAsyncWithReadOnlyMemoryWorks() - { - var expected = "Hello World!"; - - await WriteStringToStreamAsync(expected); - - Assert.Equal(expected, await ReadFromPipeAsStringAsync()); - } - - [Fact] - public async Task WriteAsyncWithArrayWorks() - { - var expected = new byte[1]; - - await WritingStream.WriteAsync(expected, 0, expected.Length); - - Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync()); - } - - [Fact] - public async Task BasicLargeWrite() - { - var expected = new byte[8000]; - - await WritingStream.WriteAsync(expected); - - Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync()); - } - - [Fact] - public void FlushAsyncIsCalledFromCallingFlush() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - - stream.Flush(); - - pipeWriter.Verify(m => m.FlushAsync(default)); - } - - [Fact] - public async Task FlushAsyncIsCalledFromCallingFlushAsync() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - - await stream.FlushAsync(); - - pipeWriter.Verify(m => m.FlushAsync(default)); - } - - [Fact] - public async Task FlushAsyncCancellationTokenIsPassedIntoFlushAsync() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - var token = new CancellationToken(); - - await stream.FlushAsync(token); - - pipeWriter.Verify(m => m.FlushAsync(token)); - } - - [Fact] - public void WriteAsyncIsCalledFromCallingWrite() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - - stream.Write(new byte[1]); - - pipeWriter.Verify(m => m.WriteAsync(It.IsAny>(), It.IsAny())); - } - - [Fact] - public async Task WriteAsyncIsCalledFromCallingWriteAsync() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - - await stream.WriteAsync(new byte[1]); - - pipeWriter.Verify(m => m.WriteAsync(It.IsAny>(), It.IsAny())); - } - - [Fact] - public async Task WriteAsyncCancellationTokenIsPassedIntoWriteAsync() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - var token = new CancellationToken(); - - await stream.WriteAsync(new byte[1], token); - - pipeWriter.Verify(m => m.WriteAsync(It.IsAny>(), token)); - } - - [Fact] - public void WriteAsyncIsCalledFromBeginWrite() - { - var pipeWriter = new Mock(); - var stream = new WriteOnlyPipeStream(pipeWriter.Object); - stream.BeginWrite(new byte[1], 0, 1, null, this); - pipeWriter.Verify(m => m.WriteAsync(It.IsAny>(), It.IsAny())); - } - - [Fact] - public async Task BeginAndEndWriteWork() - { - var expected = new byte[1]; - var asyncResult = WritingStream.BeginWrite(expected, 0, 1, null, this); - WritingStream.EndWrite(asyncResult); - Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync()); - } - - [Fact] - public void BlockSyncIOThrows() - { - var writeOnlyPipeStream = new WriteOnlyPipeStream(Writer, allowSynchronousIO: false); - Assert.Throws(() => writeOnlyPipeStream.Write(new byte[0], 0, 0)); - Assert.Throws(() => writeOnlyPipeStream.Flush()); - } - - [Fact] - public void InnerPipeWriterReturnsPipeWriter() - { - var writeOnlyPipeStream = new WriteOnlyPipeStream(Writer, allowSynchronousIO: false); - Assert.Equal(Writer, writeOnlyPipeStream.InnerPipeWriter); - } - } -} diff --git a/src/Http/Http/test/WritingAdaptersInteropTests.cs b/src/Http/Http/test/WritingAdaptersInteropTests.cs deleted file mode 100644 index 44280961b2..0000000000 --- a/src/Http/Http/test/WritingAdaptersInteropTests.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class WritingAdaptersInteropTests : PipeStreamTest - { - [Fact] - public async Task CheckBasicWritePipeApi() - { - var pipe = new Pipe(); - var writeOnlyStream = new WriteOnlyPipeStream(pipe.Writer); - var pipeWriter = new StreamPipeWriter(writeOnlyStream); - await pipeWriter.WriteAsync(new byte[10]); - - var res = await pipe.Reader.ReadAsync(); - Assert.Equal(new byte[10], res.Buffer.ToArray()); - } - - [Fact] - public async Task CheckNestedPipeApi() - { - var pipe = new Pipe(); - var writer = pipe.Writer; - for (var i = 0; i < 3; i++) - { - var writeOnlyStream = new WriteOnlyPipeStream(writer); - writer = new StreamPipeWriter(writeOnlyStream); - } - - await writer.WriteAsync(new byte[10]); - - var res = await pipe.Reader.ReadAsync(); - Assert.Equal(new byte[10], res.Buffer.ToArray()); - } - - [Fact] - public async Task CheckBasicWriteStreamApi() - { - var stream = new MemoryStream(); - var pipeWriter = new StreamPipeWriter(stream); - var writeOnlyStream = new WriteOnlyPipeStream(pipeWriter); - - await writeOnlyStream.WriteAsync(new byte[10]); - - stream.Position = 0; - var res = await ReadFromStreamAsByteArrayAsync(10, stream); - Assert.Equal(new byte[10], res); - } - - [Fact] - public async Task CheckNestedStreamApi() - { - var stream = new MemoryStream(); - Stream writeOnlyStream = stream; - for (var i = 0; i < 3; i++) - { - var pipeWriter = new StreamPipeWriter(writeOnlyStream); - writeOnlyStream = new WriteOnlyPipeStream(pipeWriter); - } - - await writeOnlyStream.WriteAsync(new byte[10]); - - stream.Position = 0; - var res = await ReadFromStreamAsByteArrayAsync(10, stream); - Assert.Equal(new byte[10], res); - } - - [Fact] - public async Task WritesCanBeCanceledViaProvidedCancellationToken() - { - var writeOnlyStream = new WriteOnlyPipeStream(new HangingPipeWriter()); - var pipeWriter = new StreamPipeWriter(writeOnlyStream); - var cts = new CancellationTokenSource(1); - await Assert.ThrowsAsync(async () => await pipeWriter.WriteAsync(new byte[1], cts.Token)); - } - - [Fact] - public async Task WriteCanBeCanceledViaCancelPendingFlushWhenFlushIsAsync() - { - var writeOnlyStream = new WriteOnlyPipeStream(new HangingPipeWriter()); - var pipeWriter = new StreamPipeWriter(writeOnlyStream); - - FlushResult flushResult = new FlushResult(); - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var task = Task.Run(async () => - { - try - { - var writingTask = pipeWriter.WriteAsync(new byte[1]); - tcs.SetResult(0); - flushResult = await writingTask; - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - throw ex; - } - }); - - await tcs.Task; - - pipeWriter.CancelPendingFlush(); - - await task; - - Assert.True(flushResult.IsCanceled); - } - - private class HangingPipeWriter : PipeWriter - { - public override void Advance(int bytes) - { - } - - public override void CancelPendingFlush() - { - throw new NotImplementedException(); - } - - public override void Complete(Exception exception = null) - { - throw new NotImplementedException(); - } - - public override async ValueTask FlushAsync(CancellationToken cancellationToken = default) - { - await Task.Delay(30000, cancellationToken); - return new FlushResult(); - } - - public override Memory GetMemory(int sizeHint = 0) - { - return new Memory(new byte[4096]); - } - - public override Span GetSpan(int sizeHint = 0) - { - return new Span(new byte[4096]); - } - - public override void OnReaderCompleted(Action callback, object state) - { - throw new NotImplementedException(); - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs index 27b385888c..be9a2c5c0f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -98,39 +98,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Stream IHttpRequestFeature.Body { - get - { - return RequestBody; - } - set - { - RequestBody = value; - var requestPipeReader = new StreamPipeReader(RequestBody, new StreamPipeReaderAdapterOptions( - minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(), - minimumReadThreshold: _context.MemoryPool.GetMinimumAllocSize(), - _context.MemoryPool)); - RequestBodyPipeReader = requestPipeReader; - - // The StreamPipeWrapper needs to be disposed as it hold onto blocks of memory - if (_wrapperObjectsToDispose == null) - { - _wrapperObjectsToDispose = new List(); - } - _wrapperObjectsToDispose.Add(requestPipeReader); - } + get => RequestBody; + set => RequestBody = value; } PipeReader IRequestBodyPipeFeature.Reader { get { + if (!ReferenceEquals(_requestStreamInternal, RequestBody)) + { + _requestStreamInternal = RequestBody; + RequestBodyPipeReader = PipeReader.Create(RequestBody); + + OnCompleted((self) => + { + ((PipeWriter)self).Complete(); + return Task.CompletedTask; + }, RequestBodyPipeReader); + } + return RequestBodyPipeReader; } - set - { - RequestBodyPipeReader = value; - RequestBody = new ReadOnlyPipeStream(RequestBodyPipeReader); - } } bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable; @@ -241,37 +230,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } + Stream IHttpResponseFeature.Body + { + get => ResponseBody; + set => ResponseBody = value; + } + PipeWriter IResponseBodyPipeFeature.Writer { get { - return ResponsePipeWriter; - } - set - { - ResponsePipeWriter = value; - ResponseBody = new WriteOnlyPipeStream(ResponsePipeWriter); - } - } - - Stream IHttpResponseFeature.Body - { - get - { - return ResponseBody; - } - set - { - ResponseBody = value; - var responsePipeWriter = new StreamPipeWriter(ResponseBody, minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(), _context.MemoryPool); - ResponsePipeWriter = responsePipeWriter; - - // The StreamPipeWrapper needs to be disposed as it hold onto blocks of memory - if (_wrapperObjectsToDispose == null) + if (!ReferenceEquals(_responseStreamInternal, ResponseBody)) { - _wrapperObjectsToDispose = new List(); + _responseStreamInternal = ResponseBody; + ResponseBodyPipeWriter = PipeWriter.Create(ResponseBody); + + OnCompleted((self) => + { + ((PipeReader)self).Complete(); + return Task.CompletedTask; + }, ResponseBodyPipeWriter); } - _wrapperObjectsToDispose.Add(responsePipeWriter); + + return ResponseBodyPipeWriter; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index ea8e77cd3b..41a4f2e6aa 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -70,8 +70,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http protected string _methodText = null; private string _scheme = null; - - private List _wrapperObjectsToDispose; + private Stream _requestStreamInternal; + private Stream _responseStreamInternal; public HttpProtocol(HttpConnectionContext context) { @@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public IHeaderDictionary ResponseHeaders { get; set; } public Stream ResponseBody { get; set; } - public PipeWriter ResponsePipeWriter { get; set; } + public PipeWriter ResponseBodyPipeWriter { get; set; } public CancellationToken RequestAborted { @@ -317,7 +317,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http bodyControl = new BodyControl(bodyControl: this, this); } - (RequestBody, ResponseBody, RequestBodyPipeReader, ResponsePipeWriter) = bodyControl.Start(messageBody); + (RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = bodyControl.Start(messageBody); + _requestStreamInternal = RequestBody; + _responseStreamInternal = ResponseBody; } public void StopBodies() => bodyControl.Stop(); @@ -400,14 +402,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http localAbortCts?.Dispose(); - if (_wrapperObjectsToDispose != null) - { - foreach (var disposable in _wrapperObjectsToDispose) - { - disposable.Dispose(); - } - } - Output?.Reset(); _requestHeadersParsed = 0; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs index 10daf49dbb..d84dfd6b90 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.IO; using System.IO.Pipelines; using System.Threading; @@ -11,26 +12,45 @@ using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - internal sealed class HttpRequestStream : ReadOnlyPipeStream + internal sealed class HttpRequestStream : Stream { private HttpRequestPipeReader _pipeReader; private readonly IHttpBodyControlFeature _bodyControl; public HttpRequestStream(IHttpBodyControlFeature bodyControl, HttpRequestPipeReader pipeReader) - : base (pipeReader) { _bodyControl = bodyControl; _pipeReader = pipeReader; } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override bool CanSeek => false; + + public override bool CanRead => true; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { - return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int WriteTimeout + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) { - return ReadAsyncInternal(destination, cancellationToken); + return ReadAsyncWrapper(destination, cancellationToken); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return ReadAsyncWrapper(new Memory(buffer, offset, count), cancellationToken).AsTask(); } public override int Read(byte[] buffer, int offset, int count) @@ -43,16 +63,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); } - private ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => throw new NotSupportedException(); + + + public override long Seek(long offset, SeekOrigin origin) { - try - { - return base.ReadAsync(buffer, cancellationToken); - } - catch (ConnectionAbortedException ex) - { - throw new TaskCanceledException("The request was aborted", ex); - } + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); } public override void Flush() @@ -63,5 +88,144 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { return Task.CompletedTask; } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = ReadAsync(buffer, offset, count, default, state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + /// + public override int EndRead(IAsyncResult asyncResult) + { + return ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = ReadAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(task2.Result); + } + }, tcs, cancellationToken); + return tcs.Task; + } + + private ValueTask ReadAsyncWrapper(Memory destination, CancellationToken cancellationToken) + { + try + { + return ReadAsyncInternal(destination, cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); + } + } + + private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + { + while (true) + { + var result = await _pipeReader.ReadAsync(cancellationToken); + + if (result.IsCanceled) + { + throw new OperationCanceledException("The read was canceled"); + } + + var readableBuffer = result.Buffer; + var readableBufferLength = readableBuffer.Length; + + var consumed = readableBuffer.End; + var actual = 0; + try + { + if (readableBufferLength != 0) + { + actual = (int)Math.Min(readableBufferLength, buffer.Length); + + var slice = actual == readableBufferLength ? readableBuffer : readableBuffer.Slice(0, actual); + consumed = slice.End; + slice.CopyTo(buffer.Span); + + return actual; + } + + if (result.IsCompleted) + { + return 0; + } + } + finally + { + _pipeReader.AdvanceTo(consumed); + } + } + + } + + /// + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + return CopyToAsyncInternal(destination, cancellationToken); + } + + private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken) + { + while (true) + { + var result = await _pipeReader.ReadAsync(cancellationToken); + var readableBuffer = result.Buffer; + var readableBufferLength = readableBuffer.Length; + + try + { + if (readableBufferLength != 0) + { + foreach (var memory in readableBuffer) + { + await destination.WriteAsync(memory, cancellationToken); + } + } + + if (result.IsCompleted) + { + return; + } + } + finally + { + _pipeReader.AdvanceTo(readableBuffer.End); + } + } + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs index 2faed0e12f..2bf5017278 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseStream.cs @@ -2,33 +2,52 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - internal sealed class HttpResponseStream : WriteOnlyPipeStream + internal sealed class HttpResponseStream : Stream { private readonly HttpResponsePipeWriter _pipeWriter; private readonly IHttpBodyControlFeature _bodyControl; public HttpResponseStream(IHttpBodyControlFeature bodyControl, HttpResponsePipeWriter pipeWriter) - : base(pipeWriter) { _bodyControl = bodyControl; _pipeWriter = pipeWriter; } - public override void Write(byte[] buffer, int offset, int count) - { - if (!_bodyControl.AllowSynchronousIO) - { - throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed); - } + public override bool CanSeek => false; - base.Write(buffer, offset, count); + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int ReadTimeout + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => throw new NotSupportedException(); + public override void Flush() { if (!_bodyControl.AllowSynchronousIO) @@ -36,7 +55,87 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed); } - base.Flush(); + FlushAsync(default).GetAwaiter().GetResult(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return _pipeWriter.FlushAsync(cancellationToken).GetAsTask(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + + public override void Write(byte[] buffer, int offset, int count) + { + if (!_bodyControl.AllowSynchronousIO) + { + throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed); + } + + WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult(); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = WriteAsync(buffer, offset, count, default, state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override void EndWrite(IAsyncResult asyncResult) + { + ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = WriteAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(null); + } + }, tcs, cancellationToken); + return tcs.Task; + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), cancellationToken); + } + + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) + { + return new ValueTask(WriteAsyncInternal(source, cancellationToken)); + } + + private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken = default) + { + return _pipeWriter.WriteAsync(source, cancellationToken).GetAsTask(); } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 7aa6ccd275..60890db5e0 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -57,30 +57,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } - [Fact] - public async Task PipesAreNotPersistedBySettingStreamPipeWriterAcrossRequests() - { - var responseBodyPersisted = false; - PipeWriter bodyPipe = null; - await using (var server = new TestServer(async context => - { - if (context.Response.BodyWriter == bodyPipe) - { - responseBodyPersisted = true; - } - bodyPipe = new StreamPipeWriter(new MemoryStream()); - context.Response.BodyWriter = bodyPipe; - - await context.Response.WriteAsync("hello, world"); - }, new TestServiceContext(LoggerFactory))) - { - Assert.Equal(string.Empty, await server.HttpClientSlim.GetStringAsync($"http://localhost:{server.Port}/")); - Assert.Equal(string.Empty, await server.HttpClientSlim.GetStringAsync($"http://localhost:{server.Port}/")); - - Assert.False(responseBodyPersisted); - } - } - [Fact] public async Task PipesAreNotPersistedAcrossRequests() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 3980092bd1..b9579f18c7 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -3851,45 +3851,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } - [Fact] - public async Task ResponseSetBodyAndPipeBodyIsWrapped() - { - await using (var server = new TestServer(async httpContext => - { - httpContext.Response.Body = new MemoryStream(); - httpContext.Response.BodyWriter = new Pipe().Writer; - Assert.IsType(httpContext.Response.Body); - - await Task.CompletedTask; - }, new TestServiceContext(LoggerFactory))) - { - using (var connection = server.CreateConnection()) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); - } - } - } - [Fact] public async Task ResponseSetBodyToSameValueTwiceGetPipeMultipleTimesDifferentObject() { await using (var server = new TestServer(async httpContext => { - var memoryStream = new MemoryStream(); - httpContext.Response.Body = memoryStream; + httpContext.Response.Body = new MemoryStream(); var BodyWriter1 = httpContext.Response.BodyWriter; - httpContext.Response.Body = memoryStream; + httpContext.Response.Body = new MemoryStream(); var BodyWriter2 = httpContext.Response.BodyWriter; Assert.NotEqual(BodyWriter1, BodyWriter2); @@ -3913,104 +3883,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } - [Fact] - public async Task ResponseSetPipeToSameValueTwiceGetBodyMultipleTimesDifferent() - { - await using (var server = new TestServer(async httpContext => - { - var pipeWriter = new Pipe().Writer; - httpContext.Response.BodyWriter = pipeWriter; - var body1 = httpContext.Response.Body; - - httpContext.Response.BodyWriter = pipeWriter; - var body2 = httpContext.Response.Body; - - Assert.NotEqual(body1, body2); - await Task.CompletedTask; - }, new TestServiceContext(LoggerFactory))) - { - using (var connection = server.CreateConnection()) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); - } - } - } - - [Fact] - public async Task ResponseSetPipeAndBodyWriterIsWrapped() - { - await using (var server = new TestServer(async httpContext => - { - httpContext.Response.BodyWriter = new Pipe().Writer; - httpContext.Response.Body = new MemoryStream(); - Assert.IsType(httpContext.Response.BodyWriter); - Assert.IsType(httpContext.Response.Body); - - await Task.CompletedTask; - }, new TestServiceContext(LoggerFactory))) - { - using (var connection = server.CreateConnection()) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); - } - } - } - - [Fact] - public async Task ResponseWriteToBodyWriterAndStreamAllBlocksDisposed() - { - await using (var server = new TestServer(async httpContext => - { - for (var i = 0; i < 3; i++) - { - httpContext.Response.BodyWriter = new Pipe().Writer; - await httpContext.Response.Body.WriteAsync(new byte[1]); - httpContext.Response.Body = new MemoryStream(); - await httpContext.Response.BodyWriter.WriteAsync(new byte[1]); - } - - // TestMemoryPool will confirm that all rented blocks have been disposed, meaning dispose was called. - - await Task.CompletedTask; - }, new TestServiceContext(LoggerFactory))) - { - using (var connection = server.CreateConnection()) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); - } - } - } - [Fact] public async Task ResponseStreamWrappingWorks() { @@ -4026,10 +3898,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests httpContext.Response.Body = oldBody; - // Even though we are restoring the original response body, we will create a - // wrapper rather than restoring the original pipe. - Assert.IsType(httpContext.Response.BodyWriter); - }, new TestServiceContext(LoggerFactory))) { using (var connection = server.CreateConnection()) @@ -4040,50 +3908,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests "", ""); await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); - } - } - } - - [Fact] - public async Task ResponsePipeWrappingWorks() - { - await using (var server = new TestServer(async httpContext => - { - var oldPipeWriter = httpContext.Response.BodyWriter; - var pipe = new Pipe(); - httpContext.Response.BodyWriter = pipe.Writer; - - await httpContext.Response.Body.WriteAsync(new byte[1]); - await httpContext.Response.BodyWriter.WriteAsync(new byte[1]); - - var readResult = await pipe.Reader.ReadAsync(); - Assert.Equal(2, readResult.Buffer.Length); - - httpContext.Response.BodyWriter = oldPipeWriter; - - // Even though we are restoring the original response body, we will create a - // wrapper rather than restoring the original pipe. - Assert.IsType(httpContext.Response.Body); - }, new TestServiceContext(LoggerFactory))) - { - using (var connection = server.CreateConnection()) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - await connection.Receive( - "HTTP/1.1 200 OK", - $"Date: {server.Context.DateHeaderValue}", - "Content-Length: 0", - "", - ""); + "HTTP/1.1 200 OK", + $"Date: {server.Context.DateHeaderValue}", + "Content-Length: 0", + "", + ""); } } }