Add StreamPipeReader (#4182)

This commit is contained in:
Justin Kotalik 2018-11-30 09:55:45 -08:00 committed by GitHub
parent e785437288
commit c06a06d16b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1420 additions and 85 deletions

1
.gitignore vendored
View File

@ -26,3 +26,4 @@ scripts/tmp/
.tools/
src/**/global.json
launchSettings.json
BenchmarkDotNet.Artifacts/

View File

@ -0,0 +1,70 @@
// 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<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return Task.FromResult<int>(0);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
return new ValueTask<int>(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<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
return default(ValueTask);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,81 @@
// 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.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<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
bytes.CopyTo(buffer, 0);
return Task.FromResult(11);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
bytes.CopyTo(buffer);
return new ValueTask<int>(11);
}
}
private class HelloWorldAsyncStream : NoopStream
{
private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await Task.Yield();
bytes.CopyTo(buffer, 0);
return 11;
}
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
await Task.Yield();
bytes.CopyTo(buffer);
return 11;
}
}
}
}

View File

@ -1,10 +1,8 @@
// 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.Text;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@ -35,55 +33,5 @@ namespace Microsoft.AspNetCore.Http
{
await _pipeWriter.WriteAsync(_largeWrite);
}
public class NoopStream : Stream
{
public override bool CanRead => false;
public override bool CanSeek => throw new System.NotImplementedException();
public override bool CanWrite => true;
public override long Length => throw new System.NotImplementedException();
public override long Position { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new System.NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new System.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<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
return default(ValueTask);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
}

View File

@ -0,0 +1,111 @@
// 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;
namespace System.IO.Pipelines
{
public sealed class BufferSegment : ReadOnlySequenceSegment<byte>
{
private IMemoryOwner<byte> _memoryOwner;
private BufferSegment _next;
private int _end;
/// <summary>
/// The Start represents the offset into AvailableMemory where the range of "active" bytes begins. At the point when the block is leased
/// the Start is guaranteed to be equal to 0. The value of Start may be assigned anywhere between 0 and
/// AvailableMemory.Length, and must be equal to or less than End.
/// </summary>
public int Start { get; private set; }
/// <summary>
/// The End represents the offset into AvailableMemory where the range of "active" bytes ends. At the point when the block is leased
/// the End is guaranteed to be equal to Start. The value of Start may be assigned anywhere between 0 and
/// Buffer.Length, and must be equal to or less than End.
/// </summary>
public int End
{
get => _end;
set
{
Debug.Assert(value - Start <= AvailableMemory.Length);
_end = value;
Memory = AvailableMemory.Slice(Start, _end - Start);
}
}
/// <summary>
/// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is
/// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous
/// working memory. The "active" memory is grown when bytes are copied in, End is increased, and Next is assigned. The "active"
/// memory is shrunk when bytes are consumed, Start is increased, and blocks are returned to the pool.
/// </summary>
public BufferSegment NextSegment
{
get => _next;
set
{
_next = value;
Next = value;
}
}
public void SetMemory(IMemoryOwner<byte> memoryOwner)
{
SetMemory(memoryOwner, 0, 0);
}
public void SetMemory(IMemoryOwner<byte> memoryOwner, int start, int end)
{
_memoryOwner = memoryOwner;
AvailableMemory = _memoryOwner.Memory;
RunningIndex = 0;
Start = start;
End = end;
NextSegment = null;
}
public void ResetMemory()
{
_memoryOwner.Dispose();
_memoryOwner = null;
AvailableMemory = default;
}
internal IMemoryOwner<byte> MemoryOwner => _memoryOwner;
public Memory<byte> AvailableMemory { get; private set; }
public int Length => End - Start;
/// <summary>
/// The amount of writable bytes in this segment. It is the amount of bytes between Length and End
/// </summary>
public int WritableBytes
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => AvailableMemory.Length - End;
}
public void SetNext(BufferSegment segment)
{
Debug.Assert(segment != null);
Debug.Assert(Next == null);
NextSegment = segment;
segment = this;
while (segment.Next != null)
{
segment.NextSegment.RunningIndex = segment.RunningIndex + segment.Length;
segment = segment.NextSegment;
}
}
}
}

View File

@ -0,0 +1,364 @@
// 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.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http
{
/// <summary>
/// Implements PipeReader using an underlying stream.
/// </summary>
public class StreamPipeReader : PipeReader
{
private readonly int _minimumSegmentSize;
private readonly Stream _readingStream;
private readonly MemoryPool<byte> _pool;
private CancellationTokenSource _internalTokenSource;
private bool _isCompleted;
private ExceptionDispatchInfo _exceptionInfo;
private BufferSegment _readHead;
private int _readIndex;
private BufferSegment _readTail;
private long _bufferedBytes;
private bool _examinedEverything;
private object _lock = new object();
private CancellationTokenSource InternalTokenSource
{
get
{
lock(_lock)
{
if (_internalTokenSource == null)
{
_internalTokenSource = new CancellationTokenSource();
}
return _internalTokenSource;
}
}
set
{
_internalTokenSource = value;
}
}
/// <summary>
/// Creates a new StreamPipeReader.
/// </summary>
/// <param name="readingStream">The stream to read from.</param>
public StreamPipeReader(Stream readingStream) : this(readingStream, minimumSegmentSize: 4096)
{
}
/// <summary>
/// Creates a new StreamPipeReader.
/// </summary>
/// <param name="readingStream">The stream to read from.</param>
/// <param name="minimumSegmentSize">The minimum segment size to return from ReadAsync.</param>
/// <param name="pool"></param>
public StreamPipeReader(Stream readingStream, int minimumSegmentSize, MemoryPool<byte> pool = null)
{
_minimumSegmentSize = minimumSegmentSize;
_readingStream = readingStream;
_pool = pool ?? MemoryPool<byte>.Shared;
}
/// <inheritdoc />
public override void AdvanceTo(SequencePosition consumed)
{
AdvanceTo(consumed, consumed);
}
/// <inheritdoc />
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<byte>(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 - _readTail.Start;
}
// 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<byte> 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;
}
}
/// <inheritdoc />
public override void CancelPendingRead()
{
InternalTokenSource.Cancel();
}
/// <inheritdoc />
public override void Complete(Exception exception = null)
{
if (_isCompleted)
{
return;
}
_isCompleted = true;
if (exception != null)
{
_exceptionInfo = ExceptionDispatchInfo.Capture(exception);
}
var segment = _readHead;
while (segment != null)
{
segment.ResetMemory();
segment = segment.NextSegment;
}
}
/// <inheritdoc />
public override void OnWriterCompleted(Action<Exception, object> callback, object state)
{
throw new NotSupportedException("OnWriterCompleted is not supported");
}
/// <inheritdoc />
public override async ValueTask<ReadResult> 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;
}
var reg = new CancellationTokenRegistration();
if (cancellationToken.CanBeCanceled)
{
reg = cancellationToken.Register(state => ((StreamPipeReader)state).Cancel(), this);
}
using (reg)
{
var isCanceled = false;
try
{
AllocateReadTail();
#if NETCOREAPP2_2
var length = await _readingStream.ReadAsync(_readTail.AvailableMemory.Slice(_readTail.End), tokenSource.Token);
#elif NETSTANDARD2_0
if (!MemoryMarshal.TryGetArray<byte>(_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;
}
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 (_isCompleted)
{
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)
{
// 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<byte> GetCurrentReadOnlySequence()
{
return new ReadOnlySequence<byte>(_readHead, _readIndex, _readTail, _readTail.End - _readTail.Start);
}
private void AllocateReadTail()
{
if (_readHead == null)
{
Debug.Assert(_readTail == null);
_readHead = CreateBufferSegment();
_readHead.SetMemory(_pool.Rent(GetSegmentSize()));
_readTail = _readHead;
}
else if (_readTail.WritableBytes == 0)
{
CreateNewTailSegment();
}
}
private void CreateNewTailSegment()
{
var nextSegment = CreateBufferSegment();
nextSegment.SetMemory(_pool.Rent(GetSegmentSize()));
_readTail.SetNext(nextSegment);
_readTail = nextSegment;
}
private int GetSegmentSize() => Math.Min(_pool.MaxBufferSize, _minimumSegmentSize);
private BufferSegment CreateBufferSegment()
{
// TODO this can pool buffer segment objects
return new BufferSegment();
}
private void Cancel()
{
InternalTokenSource.Cancel();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsCompletedOrThrow()
{
if (!_isCompleted)
{
return false;
}
if (_exceptionInfo != null)
{
ThrowLatchedException();
}
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowLatchedException()
{
_exceptionInfo.Throw();
}
public void Dispose()
{
Complete();
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;

View File

@ -0,0 +1,23 @@
// 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.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Http
{
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.");
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<TargetFrameworks>net461</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -11,19 +11,25 @@ namespace Microsoft.AspNetCore.Http.Tests
{
protected const int MaximumSizeHigh = 65;
protected const int MinimumSegmentSize = 4096;
public MemoryStream MemoryStream { get; set; }
public PipeWriter Writer { get; set; }
public PipeReader Reader { get; set; }
protected PipeTest()
{
MemoryStream = new MemoryStream();
Writer = new StreamPipeWriter(MemoryStream, 4096, new TestMemoryPool());
Writer = new StreamPipeWriter(MemoryStream, MinimumSegmentSize, new TestMemoryPool());
Reader = new StreamPipeReader(MemoryStream, MinimumSegmentSize, new TestMemoryPool());
}
public void Dispose()
{
Writer.Complete();
Reader.Complete();
}
public byte[] Read()
@ -32,6 +38,17 @@ namespace Microsoft.AspNetCore.Http.Tests
return ReadWithoutFlush();
}
public void Write(byte[] data)
{
MemoryStream.Write(data, 0, data.Length);
MemoryStream.Position = 0;
}
public void WriteWithoutPosition(byte[] data)
{
MemoryStream.Write(data, 0, data.Length);
}
public byte[] ReadWithoutFlush()
{
MemoryStream.Position = 0;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Http.Tests
{
var span = Writer.GetSpan(0);
var secondSpan = Writer.GetSpan(8000);
var secondSpan = Writer.GetSpan(10000);
Assert.False(span.SequenceEqual(secondSpan));
}

View File

@ -0,0 +1,128 @@
// 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 Microsoft.AspNetCore.Http.Tests
{
public class ReadAsyncCancellationTests : PipeTest
{
[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<byte> 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<ReadResult> 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);
}
}
}

View File

@ -0,0 +1,503 @@
// 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.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Http.Tests
{
public partial class StreamPipeReaderTests : PipeTest
{
[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()
{
Write(new byte[10000]);
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()
{
Reader = new StreamPipeReader(MemoryStream, 4095, new TestMemoryPool());
Write(new byte[10000]);
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()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(new byte[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<TaskCanceledException>(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<int>(TaskCreationOptions.RunContinuationsAsynchronously);
var task = Task.Run(async () =>
{
var writingTask = pipeReader.ReadAsync();
tcs.SetResult(0);
result = await writingTask;
});
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()
{
// 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<ArgumentOutOfRangeException>(() => Reader.AdvanceTo(buffer.End));
Reader.AdvanceTo(result.Buffer.End);
}
[Fact]
public void AdvanceWithoutReadingWithValidSequencePosition()
{
var sequencePosition = new SequencePosition(new BufferSegment(), 5);
Assert.Throws<InvalidOperationException>(() => Reader.AdvanceTo(sequencePosition));
}
[Fact]
public async Task AdvanceMultipleSegments()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(new byte[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()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(new byte[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()
{
Write(new byte[100]);
await Reader.ReadAsync();
Reader.Complete();
}
[Fact]
public async Task AdvanceAfterCompleteThrows()
{
Write(new byte[100]);
var buffer = (await Reader.ReadAsync()).Buffer;
Reader.Complete();
var exception = Assert.Throws<InvalidOperationException>(() => Reader.AdvanceTo(buffer.End));
Assert.Equal("Reading is not allowed after reader was completed.", exception.Message);
}
[Fact]
public async Task ReadBetweenBlocks()
{
var blockSize = 16;
Reader = new StreamPipeReader(MemoryStream, blockSize, new TestMemoryPool());
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<ReadOnlyMemory<byte>>();
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<InvalidOperationException>(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()
{
Write(new byte[20]);
var task = Reader.ReadAsync();
Assert.True(IsTaskWithResult(task));
}
[Fact]
public void CancelledReadAsyncReturnsTaskWithValue()
{
Reader.CancelPendingRead();
var task = Reader.ReadAsync();
Assert.True(IsTaskWithResult(task));
}
[Fact]
public async Task AdvancePastMinReadSizeReadAsyncReturnsMoreData()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(new byte[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()
{
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();
Reader.AdvanceTo(readResult2.Buffer.GetPosition(2000));
var readResult3 = await Reader.ReadAsync();
Assert.Equal(6192, readResult3.Buffer.Length);
}
[Fact]
public async Task ReadMultipleTimesAdvanceFreesAppropriately()
{
var blockSize = 16;
var pool = new TestMemoryPool();
Reader = new StreamPipeReader(MemoryStream, blockSize, pool);
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
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()
{
MemoryStream = new AsyncStream();
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
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()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
var readResult = await Reader.ReadAsync();
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
MemoryStream.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()
{
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
var readResult = await Reader.ReadAsync();
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
MemoryStream.Position = 0;
readResult = await Reader.ReadAsync();
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
MemoryStream.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);
}
private bool IsTaskWithResult<T>(ValueTask<T> task)
{
return task == new ValueTask<T>(task.Result);
}
private class AsyncStream : MemoryStream
{
private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await Task.Yield();
return await base.ReadAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_2
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
await Task.Yield();
return await base.ReadAsync(buffer, cancellationToken);
}
#endif
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Http.Tests
[Fact]
public async Task CanWriteAsyncMultipleTimesIntoSameBlock()
{
await Writer.WriteAsync(new byte[] { 1 });
await Writer.WriteAsync(new byte[] { 2 });
await Writer.WriteAsync(new byte[] { 3 });
@ -313,6 +312,13 @@ namespace Microsoft.AspNetCore.Http.Tests
await Task.Delay(30000, cancellationToken);
return 0;
}
#if NETCOREAPP2_2
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
await Task.Delay(30000, cancellationToken);
return 0;
}
#endif
}
internal class SingleWriteStream : MemoryStream

View File

@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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.
@ -13,16 +13,27 @@ namespace Microsoft.AspNetCore.Http.Tests
{
public class TestMemoryPool : MemoryPool<byte>
{
private MemoryPool<byte> _pool = Shared;
private MemoryPool<byte> _pool;
private bool _disposed;
private int _rentCount;
public TestMemoryPool()
{
_pool = new CustomMemoryPool<byte>();
}
public override IMemoryOwner<byte> 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;
@ -65,6 +76,7 @@ namespace Microsoft.AspNetCore.Http.Tests
protected override void Dispose(bool disposing)
{
_pool._rentCount--;
_pool.CheckDisposed();
}
@ -135,5 +147,58 @@ namespace Microsoft.AspNetCore.Http.Tests
return _owner.Memory.Span;
}
}
private class CustomMemoryPool<T> : MemoryPool<T>
{
public override int MaxBufferSize => int.MaxValue;
public override IMemoryOwner<T> 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<T>
{
private T[] _array;
public ArrayMemoryPoolBuffer(int size)
{
_array = new T[size];
}
public Memory<T> Memory
{
get
{
T[] array = _array;
if (array == null)
{
throw new ObjectDisposedException(nameof(array));
}
return new Memory<T>(array);
}
}
public void Dispose()
{
T[] array = _array;
if (array != null)
{
_array = null;
}
}
}
}
}
}
}

View File

@ -5,59 +5,61 @@ VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Abstractions", "Authentication.Abstractions", "{587C3D55-6092-4B86-99F5-E9772C9C1ADB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Abstractions", "Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{565B7B00-96A1-49B8-9753-9E045C6527A2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Abstractions", "Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{565B7B00-96A1-49B8-9753-9E045C6527A2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Core", "Authentication.Core", "{B51F45A6-428F-40F4-897F-7C62C29EC39A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core", "Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj", "{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core", "Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj", "{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core.Test", "Authentication.Core\test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{21071749-4361-4CD0-B5ED-541C72326800}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core.Test", "Authentication.Core\test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{21071749-4361-4CD0-B5ED-541C72326800}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headers", "Headers", "{FF334B62-1AE2-477C-B91B-B28F898DFC3A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers", "Headers\src\Microsoft.Net.Http.Headers.csproj", "{D2B2E73E-A3A4-4996-906C-6647CD7D2634}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Http.Headers", "Headers\src\Microsoft.Net.Http.Headers.csproj", "{D2B2E73E-A3A4-4996-906C-6647CD7D2634}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers.Tests", "Headers\test\Microsoft.Net.Http.Headers.Tests.csproj", "{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Http.Headers.Tests", "Headers\test\Microsoft.Net.Http.Headers.Tests.csproj", "{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http", "Http\src\Microsoft.AspNetCore.Http.csproj", "{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http", "Http\src\Microsoft.AspNetCore.Http.csproj", "{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Tests", "Http\test\Microsoft.AspNetCore.Http.Tests.csproj", "{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Tests", "Http\test\Microsoft.AspNetCore.Http.Tests.csproj", "{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Abstractions", "Http.Abstractions", "{28F3D5CC-1F8E-4E15-94C8-E432DFA0A702}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions", "Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj", "{D079CD1C-A18F-4457-91BC-432577D2FD37}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions", "Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj", "{D079CD1C-A18F-4457-91BC-432577D2FD37}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions.Tests", "Http.Abstractions\test\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", "{C28045AC-FF16-468C-A1E8-EC192DA2EF19}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions.Tests", "Http.Abstractions\test\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", "{C28045AC-FF16-468C-A1E8-EC192DA2EF19}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Extensions", "Http.Extensions", "{CCC61332-7D63-4DDB-B604-884670157624}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions", "Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj", "{C06F2A33-B887-46BB-8F51-2666EDBE5D38}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Extensions", "Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj", "{C06F2A33-B887-46BB-8F51-2666EDBE5D38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions.Tests", "Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Extensions.Tests", "Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Features", "Http.Features", "{0B1B3E58-DA37-46D6-B791-47739EF27790}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features", "Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj", "{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Features", "Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj", "{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features.Tests", "Http.Features\test\Microsoft.AspNetCore.Http.Features.Tests.csproj", "{5A64C915-7045-4100-B2CB-3A50BD854D2D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Features.Tests", "Http.Features\test\Microsoft.AspNetCore.Http.Features.Tests.csproj", "{5A64C915-7045-4100-B2CB-3A50BD854D2D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Owin", "Owin", "{4D5C4F16-5DC5-4244-A10F-08545126F61B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin", "Owin\src\Microsoft.AspNetCore.Owin.csproj", "{21624719-422E-4621-A17A-C6F10436F1FE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Owin", "Owin\src\Microsoft.AspNetCore.Owin.csproj", "{21624719-422E-4621-A17A-C6F10436F1FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin.Tests", "Owin\test\Microsoft.AspNetCore.Owin.Tests.csproj", "{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Owin.Tests", "Owin\test\Microsoft.AspNetCore.Owin.Tests.csproj", "{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{391FBA36-BEEB-411A-A588-3F83901C0C1A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{2378049E-ABE9-4843-AAC7-A6C9E704463D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{2378049E-ABE9-4843-AAC7-A6C9E704463D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebUtilities", "WebUtilities", "{80A090C8-ED02-4DE3-875A-30DCCDBD84BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities", "WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{1A866315-5FD5-4F96-BFAC-1447E3CB4514}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities", "WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{1A866315-5FD5-4F96-BFAC-1447E3CB4514}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities.Tests", "WebUtilities\test\Microsoft.AspNetCore.WebUtilities.Tests.csproj", "{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities.Tests", "WebUtilities\test\Microsoft.AspNetCore.WebUtilities.Tests.csproj", "{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Performance", "Http\perf\Microsoft.AspNetCore.Http.Performance.csproj", "{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -68,9 +70,6 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
@ -288,6 +287,21 @@ Global
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x64.Build.0 = Release|Any CPU
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.ActiveCfg = Release|Any CPU
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.Build.0 = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x64.Build.0 = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x86.Build.0 = Debug|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|Any CPU.Build.0 = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x64.ActiveCfg = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x64.Build.0 = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x86.ActiveCfg = Release|Any CPU
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{565B7B00-96A1-49B8-9753-9E045C6527A2} = {587C3D55-6092-4B86-99F5-E9772C9C1ADB}
@ -308,5 +322,9 @@ Global
{2378049E-ABE9-4843-AAC7-A6C9E704463D} = {391FBA36-BEEB-411A-A588-3F83901C0C1A}
{1A866315-5FD5-4F96-BFAC-1447E3CB4514} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F} = {FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {39F96525-F455-424F-ABDB-33DB59861EA6}
EndGlobalSection
EndGlobal