diff --git a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
index 380a4bd959..2f17e52019 100644
--- a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
+++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
@@ -22,7 +22,6 @@ Microsoft.AspNetCore.Http.HttpResponse
-
diff --git a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
index 0c996ff691..503c205276 100644
--- a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
+++ b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
@@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Http.Features
public interface IRequestBodyPipeFeature
{
///
- /// A representing the request body, if any.
+ /// A representing the request body, if any.
///
PipeReader RequestBodyPipe { get; set; }
}
diff --git a/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj b/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj
index c83d286a00..6a80fe588a 100644
--- a/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj
+++ b/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.0;net461
+ netcoreapp3.0
diff --git a/src/Http/Http/src/BufferSegment.cs b/src/Http/Http/src/BufferSegment.cs
index f0dcdc5077..735a4a39e0 100644
--- a/src/Http/Http/src/BufferSegment.cs
+++ b/src/Http/Http/src/BufferSegment.cs
@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
namespace System.IO.Pipelines
{
- public sealed class BufferSegment : ReadOnlySequenceSegment
+ internal sealed class BufferSegment : ReadOnlySequenceSegment
{
private IMemoryOwner _memoryOwner;
private BufferSegment _next;
diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
index 1575488b80..d091f7d690 100644
--- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
+++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
@@ -1,4 +1,4 @@
-
+
ASP.NET Core default HTTP feature implementations.
@@ -19,7 +19,6 @@
-
diff --git a/src/Http/Http/src/Properties/AssemblyInfo.cs b/src/Http/Http/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2b8d94f4a5
--- /dev/null
+++ b/src/Http/Http/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Http.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Http/Http/src/ReadOnlyPipeStream.cs b/src/Http/Http/src/ReadOnlyPipeStream.cs
new file mode 100644
index 0000000000..7585947d2c
--- /dev/null
+++ b/src/Http/Http/src/ReadOnlyPipeStream.cs
@@ -0,0 +1,240 @@
+// 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 readonly PipeReader _pipeReader;
+ 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;
+ _pipeReader = 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 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 _pipeReader.ReadAsync(cancellationToken);
+ 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/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs
index 9d9e64caca..09295f2807 100644
--- a/src/Http/Http/src/StreamPipeReader.cs
+++ b/src/Http/Http/src/StreamPipeReader.cs
@@ -25,7 +25,8 @@ namespace System.IO.Pipelines
private readonly MemoryPool _pool;
private CancellationTokenSource _internalTokenSource;
- private bool _isCompleted;
+ private bool _isReaderCompleted;
+ private bool _isWriterCompleted;
private ExceptionDispatchInfo _exceptionInfo;
private BufferSegment _readHead;
@@ -182,12 +183,12 @@ namespace System.IO.Pipelines
///
public override void Complete(Exception exception = null)
{
- if (_isCompleted)
+ if (_isReaderCompleted)
{
return;
}
- _isCompleted = true;
+ _isReaderCompleted = true;
if (exception != null)
{
_exceptionInfo = ExceptionDispatchInfo.Capture(exception);
@@ -248,6 +249,11 @@ namespace System.IO.Pipelines
_readTail.End += length;
_bufferedBytes += length;
+
+ if (length == 0)
+ {
+ _isWriterCompleted = true;
+ }
}
catch (OperationCanceledException)
{
@@ -275,7 +281,7 @@ namespace System.IO.Pipelines
private void ThrowIfCompleted()
{
- if (_isCompleted)
+ if (_isReaderCompleted)
{
ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed();
}
@@ -357,7 +363,7 @@ namespace System.IO.Pipelines
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsCompletedOrThrow()
{
- if (!_isCompleted)
+ if (!_isWriterCompleted)
{
return false;
}
diff --git a/src/Http/Http/src/StreamPipeWriter.cs b/src/Http/Http/src/StreamPipeWriter.cs
index 6926f1e9b9..adfafe4455 100644
--- a/src/Http/Http/src/StreamPipeWriter.cs
+++ b/src/Http/Http/src/StreamPipeWriter.cs
@@ -65,7 +65,7 @@ namespace System.IO.Pipelines
}
///
- /// Gets the inner stream that is being read from.
+ /// Gets the inner stream that is being written to.
///
public Stream InnerStream => _writingStream;
diff --git a/src/Http/Http/src/ThrowHelper.cs b/src/Http/Http/src/ThrowHelper.cs
index e671d9f6ee..1ae116b646 100644
--- a/src/Http/Http/src/ThrowHelper.cs
+++ b/src/Http/Http/src/ThrowHelper.cs
@@ -19,5 +19,17 @@ namespace System.IO.Pipelines
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.");
}
}
diff --git a/src/Http/Http/src/WriteOnlyPipeStream.cs b/src/Http/Http/src/WriteOnlyPipeStream.cs
new file mode 100644
index 0000000000..0f5121cc2f
--- /dev/null
+++ b/src/Http/Http/src/WriteOnlyPipeStream.cs
@@ -0,0 +1,162 @@
+// 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;
+
+namespace System.IO.Pipelines
+{
+ ///
+ /// Represents a WriteOnlyStream backed by a PipeWriter
+ ///
+ public class WriteOnlyPipeStream : Stream
+ {
+ private PipeWriter _pipeWriter;
+ 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)
+ {
+ _pipeWriter = 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 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 async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await _pipeWriter.FlushAsync(cancellationToken);
+ }
+
+ ///
+ 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