From 73d4df5fe736c76ec29c6235f1dd13d76fbe4e0b Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 3 Jan 2019 12:10:35 -0800 Subject: [PATCH 01/14] Implement PipeBody Features and add to HttpContext --- src/Http/Http.Abstractions/src/HttpRequest.cs | 6 ++ .../Http.Abstractions/src/HttpResponse.cs | 6 ++ ...rosoft.AspNetCore.Http.Abstractions.csproj | 1 + .../src/IRequestBodyPipeFeature.cs | 18 +++++ .../src/IResponseBodyPipeFeature.cs | 18 +++++ .../Microsoft.AspNetCore.Http.Features.csproj | 3 +- .../src/Features/RequestBodyPipeFeature.cs | 56 +++++++++++++ .../src/Features/ResponseBodyPipeFeature.cs | 56 +++++++++++++ .../Http/src/Internal/DefaultHttpRequest.cs | 11 +++ .../Http/src/Internal/DefaultHttpResponse.cs | 11 +++ src/Http/Http/src/StreamPipeReader.cs | 5 ++ src/Http/Http/src/StreamPipeWriter.cs | 5 ++ .../Features/RequestBodyPipeFeatureTests.cs | 44 ++++++++++ .../Features/ResponseBodyPipeFeatureTests.cs | 44 ++++++++++ .../test/Internal/DefaultHttpRequestTests.cs | 41 ++++++++++ .../test/Internal/DefaultHttpResponseTests.cs | 41 ++++++++++ src/Http/Http/test/StreamPipeReaderTests.cs | 80 +++++++++++++++++-- src/Http/Http/test/StreamPipeWriterTests.cs | 66 +++++++++++++-- 18 files changed, 499 insertions(+), 13 deletions(-) create mode 100644 src/Http/Http.Features/src/IRequestBodyPipeFeature.cs create mode 100644 src/Http/Http.Features/src/IResponseBodyPipeFeature.cs create mode 100644 src/Http/Http/src/Features/RequestBodyPipeFeature.cs create mode 100644 src/Http/Http/src/Features/ResponseBodyPipeFeature.cs create mode 100644 src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs create mode 100644 src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs diff --git a/src/Http/Http.Abstractions/src/HttpRequest.cs b/src/Http/Http.Abstractions/src/HttpRequest.cs index 4c4d0d1af15e..6c7bd52372c0 100644 --- a/src/Http/Http.Abstractions/src/HttpRequest.cs +++ b/src/Http/Http.Abstractions/src/HttpRequest.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.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; @@ -102,6 +103,11 @@ public abstract class HttpRequest /// The RequestBody Stream. public abstract Stream Body { get; set; } + /// + /// Gets or sets the request body pipe + /// + public abstract PipeReader BodyPipe { get; set; } + /// /// 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 8a1e5d490829..e5af10fbfa5e 100644 --- a/src/Http/Http.Abstractions/src/HttpResponse.cs +++ b/src/Http/Http.Abstractions/src/HttpResponse.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.IO.Pipelines; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Http @@ -39,6 +40,11 @@ public abstract class HttpResponse /// public abstract Stream Body { get; set; } + /// + /// Gets or sets the response body pipe + /// + public abstract PipeWriter BodyPipe { get; set; } + /// /// Gets or sets the value for the Content-Length response header. /// 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 2f17e520197a..380a4bd95995 100644 --- a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj +++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj @@ -22,6 +22,7 @@ Microsoft.AspNetCore.Http.HttpResponse + diff --git a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs new file mode 100644 index 000000000000..d2ea00745fbb --- /dev/null +++ b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs @@ -0,0 +1,18 @@ +// 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.Pipelines; + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// Represents the HttpRequestBody as a PipeReader + /// + public interface IRequestBodyPipeFeature + { + /// + /// A representing the request body, if any. + /// + PipeReader PipeReader { get; set; } + } +} diff --git a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs new file mode 100644 index 000000000000..cf129096ef02 --- /dev/null +++ b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs @@ -0,0 +1,18 @@ +// 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.Pipelines; + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// Represents the HttpResponseBody as a PipeWriter + /// + public interface IResponseBodyPipeFeature + { + /// + /// A representing the response body, if any. + /// + PipeWriter PipeWriter { get; set; } + } +} diff --git a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj index f1cf8b0b9eee..90f707c6437b 100644 --- a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj +++ b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core HTTP feature interface definitions. @@ -11,6 +11,7 @@ + diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs new file mode 100644 index 000000000000..d503c571e296 --- /dev/null +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -0,0 +1,56 @@ +// 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; + +namespace Microsoft.AspNetCore.Http.Features +{ + public class RequestBodyPipeFeature : IRequestBodyPipeFeature + { + // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624 + private readonly static Func _nullRequestFeature = f => null; + + private PipeReader _pipeReader; + private FeatureReferences _features; + + public RequestBodyPipeFeature(IFeatureCollection features) + { + if (features == null) + { + throw new ArgumentNullException(nameof(features)); + } + + _features = new FeatureReferences(features); + } + + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache, _nullRequestFeature); + + public PipeReader PipeReader + { + get + { + if (_pipeReader == null) + { + _pipeReader = new StreamPipeReader(HttpRequestFeature.Body); + } + + return _pipeReader; + } + set + { + _pipeReader = value; + if (_pipeReader == null) + { + HttpRequestFeature.Body = null; + } + else + { + // TODO set the Request body + // HttpRequestFeature.Body = new PipeStreamReader(); + } + } + } + } +} diff --git a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs new file mode 100644 index 000000000000..ef326756dc56 --- /dev/null +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -0,0 +1,56 @@ +// 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.IO.Pipelines; + +namespace Microsoft.AspNetCore.Http.Features +{ + public class ResponseBodyPipeFeature : IResponseBodyPipeFeature + { + private readonly static Func _nullRequestFeature = f => null; + + private PipeWriter _pipeWriter; + private FeatureReferences _features; + + public ResponseBodyPipeFeature(IFeatureCollection features) + { + if (features == null) + { + throw new ArgumentNullException(nameof(features)); + } + + _features = new FeatureReferences(features); + } + + private IHttpResponseFeature HttpResponseFeature => + _features.Fetch(ref _features.Cache, _nullRequestFeature); + + public PipeWriter PipeWriter + { + get + { + if (_pipeWriter == null) + { + _pipeWriter = new StreamPipeWriter(HttpResponseFeature.Body); + } + + return _pipeWriter; + } + set + { + _pipeWriter = value; + if (_pipeWriter == null) + { + HttpResponseFeature.Body = Stream.Null; + } + else + { + // TODO set the Response body + // HttpResponseFeature.Body = new PipeStreamWriter(); + } + } + } + } +} diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs index cf8ac92a3b5f..c04f5573fafc 100644 --- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs +++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; @@ -19,6 +20,7 @@ public class DefaultHttpRequest : HttpRequest private readonly static Func _newFormFeature = r => new FormFeature(r); private readonly static Func _newRequestCookiesFeature = f => new RequestCookiesFeature(f); private readonly static Func _newRouteValuesFeature = f => new RouteValuesFeature(); + private readonly static Func _newRequestBodyPipeFeature = f => new RequestBodyPipeFeature(f); private HttpContext _context; private FeatureReferences _features; @@ -57,6 +59,9 @@ public virtual void Uninitialize() private IRouteValuesFeature RouteValuesFeature => _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature); + private IRequestBodyPipeFeature RequestBodyPipeFeature => + _features.Fetch(ref _features.Cache.BodyPipe, _newRequestBodyPipeFeature); + public override PathString PathBase { get { return new PathString(HttpRequestFeature.PathBase); } @@ -162,6 +167,11 @@ public override RouteValueDictionary RouteValues set { RouteValuesFeature.RouteValues = value; } } + public override PipeReader BodyPipe { + get { return RequestBodyPipeFeature.PipeReader; } + set { RequestBodyPipeFeature.PipeReader = value; } + } + struct FeatureInterfaces { public IHttpRequestFeature Request; @@ -169,6 +179,7 @@ struct FeatureInterfaces public IFormFeature Form; public IRequestCookiesFeature Cookies; public IRouteValuesFeature RouteValues; + public IRequestBodyPipeFeature BodyPipe; } } } diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs index 6a812426d850..3ec814038822 100644 --- a/src/Http/Http/src/Internal/DefaultHttpResponse.cs +++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; using Microsoft.Net.Http.Headers; @@ -14,6 +15,7 @@ public class DefaultHttpResponse : HttpResponse // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func _nullResponseFeature = f => null; private readonly static Func _newResponseCookiesFeature = f => new ResponseCookiesFeature(f); + private readonly static Func _newResponseBodyPipeFeature = f => new ResponseBodyPipeFeature(f); private HttpContext _context; private FeatureReferences _features; @@ -41,6 +43,8 @@ public virtual void Uninitialize() private IResponseCookiesFeature ResponseCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature); + private IResponseBodyPipeFeature ResponseBodyPipeFeature => + _features.Fetch(ref _features.Cache.BodyPipe, _newResponseBodyPipeFeature); public override HttpContext HttpContext { get { return _context; } } @@ -96,6 +100,12 @@ public override bool HasStarted get { return HttpResponseFeature.HasStarted; } } + public override PipeWriter BodyPipe + { + get { return ResponseBodyPipeFeature.PipeWriter; } + set { ResponseBodyPipeFeature.PipeWriter = value; } + } + public override void OnStarting(Func callback, object state) { if (callback == null) @@ -134,6 +144,7 @@ struct FeatureInterfaces { public IHttpResponseFeature Response; public IResponseCookiesFeature Cookies; + public IResponseBodyPipeFeature BodyPipe; } } } diff --git a/src/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs index cd62041bed39..33919396b995 100644 --- a/src/Http/Http/src/StreamPipeReader.cs +++ b/src/Http/Http/src/StreamPipeReader.cs @@ -70,6 +70,11 @@ public StreamPipeReader(Stream readingStream, StreamPipeReaderOptions options) _pool = options.MemoryPool; } + /// + /// Gets the inner stream that is being read from. + /// + public Stream InnerStream { get { return _readingStream; } } + /// public override void AdvanceTo(SequencePosition consumed) { diff --git a/src/Http/Http/src/StreamPipeWriter.cs b/src/Http/Http/src/StreamPipeWriter.cs index f93950feec98..ad6d2c3b5f3b 100644 --- a/src/Http/Http/src/StreamPipeWriter.cs +++ b/src/Http/Http/src/StreamPipeWriter.cs @@ -64,6 +64,11 @@ public StreamPipeWriter(Stream writingStream, int minimumSegmentSize, MemoryPool _pool = pool ?? MemoryPool.Shared; } + /// + /// Gets the inner stream that is being written to. + /// + public Stream InnerStream { get { return _writingStream; } } + /// public override void Advance(int count) { diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs new file mode 100644 index 000000000000..81c3c1db9f9b --- /dev/null +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -0,0 +1,44 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Http.Features +{ + public class RequestBodyPipeFeatureTests + { + [Fact] + public void RequestBodyReturnsStreamPipeReader() + { + var features = new FeatureCollection(); + var request = new HttpRequestFeature(); + var expectedStream = new MemoryStream(); + request.Body = expectedStream; + features[typeof(IHttpRequestFeature)] = request; + + var provider = new RequestBodyPipeFeature(features); + + var pipeBody = provider.PipeReader; + + Assert.True(pipeBody is StreamPipeReader); + Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); + } + + [Fact] + public void RequestBodySetPipeReaderReturnsSameValue() + { + var features = new FeatureCollection(); + var request = new HttpRequestFeature(); + features[typeof(IHttpRequestFeature)] = request; + + var provider = new RequestBodyPipeFeature(features); + + var pipeReader = new Pipe().Reader; + provider.PipeReader = pipeReader; + + Assert.Equal(pipeReader, provider.PipeReader); + } + } +} diff --git a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs new file mode 100644 index 000000000000..d713d9a1fb05 --- /dev/null +++ b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs @@ -0,0 +1,44 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Http.Features +{ + public class ResponseBodyPipeFeatureTests + { + [Fact] + public void ResponseBodyReturnsStreamPipeReader() + { + var features = new FeatureCollection(); + var response = new HttpResponseFeature(); + var expectedStream = new MemoryStream(); + response.Body = expectedStream; + features[typeof(IHttpResponseFeature)] = response; + + var provider = new ResponseBodyPipeFeature(features); + + var pipeBody = provider.PipeWriter; + + Assert.True(pipeBody is StreamPipeWriter); + Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); + } + + [Fact] + public void ResponseBodySetPipeReaderReturnsSameValue() + { + var features = new FeatureCollection(); + var response = new HttpResponseFeature(); + features[typeof(IHttpResponseFeature)] = response; + + var provider = new ResponseBodyPipeFeature(features); + + var pipeWriter = new Pipe().Writer; + provider.PipeWriter = pipeWriter; + + Assert.Equal(pipeWriter, provider.PipeWriter); + } + } +} diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs index 09e47a962e1c..5def8575ae07 100644 --- a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.IO.Pipelines; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; @@ -241,6 +243,45 @@ public void RouteValues_GetAndSet() Assert.Empty(request.RouteValues); } + [Fact] + public void BodyPipe_CanGet() + { + var context = new DefaultHttpContext(); + var bodyPipe = context.Request.BodyPipe; + Assert.NotNull(bodyPipe); + } + + [Fact] + public void BodyPipe_CanSet() + { + var pipeReader = new Pipe().Reader; + var context = new DefaultHttpContext(); + + context.Request.BodyPipe = pipeReader; + + Assert.Equal(pipeReader, context.Request.BodyPipe); + } + + [Fact] + public void BodyPipe_WrapsStream() + { + var context = new DefaultHttpContext(); + var expectedStream = new MemoryStream(); + context.Request.Body = expectedStream; + + var bodyPipe = context.Request.BodyPipe as StreamPipeReader; + + Assert.Equal(expectedStream, bodyPipe.InnerStream); + } + + [Fact] + public void BodyPipe_NullOutBody() + { + var context = new DefaultHttpContext(); + context.Request.BodyPipe = null; + Assert.Null(context.Request.Body); + } + 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 4764c44a63e0..c25455f1aa7d 100644 --- a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.IO.Pipelines; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; using Xunit; @@ -59,6 +61,45 @@ public void GetContentType_ReturnsNullIfHeaderDoesNotExist() Assert.Null(response.ContentType); } + [Fact] + public void BodyPipe_CanGet() + { + var response = new DefaultHttpContext(); + var bodyPipe = response.Response.BodyPipe; + + Assert.NotNull(bodyPipe); + } + + [Fact] + public void BodyPipe_CanSet() + { + var response = new DefaultHttpContext(); + var pipeWriter = new Pipe().Writer; + response.Response.BodyPipe = pipeWriter; + + Assert.Equal(pipeWriter, response.Response.BodyPipe); + } + + [Fact] + public void BodyPipe_WrapsStream() + { + var context = new DefaultHttpContext(); + var expectedStream = new MemoryStream(); + context.Response.Body = expectedStream; + + var bodyPipe = context.Response.BodyPipe as StreamPipeWriter; + + Assert.Equal(expectedStream, bodyPipe.InnerStream); + } + + [Fact] + public void BodyPipe_NullOutBody() + { + var context = new DefaultHttpContext(); + context.Response.BodyPipe = null; + Assert.Equal(Stream.Null, context.Response.Body); + } + private static HttpResponse CreateResponse(IHeaderDictionary headers) { var context = new DefaultHttpContext(); diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs index 97e8f3ab631a..55c7cf27f30a 100644 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ b/src/Http/Http/test/StreamPipeReaderTests.cs @@ -1,11 +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.Buffers; using System.Collections.Generic; -using System.IO; -using System.IO.Pipelines; using System.Linq; using System.Text; using System.Threading; @@ -151,7 +148,7 @@ public async Task ReadExamineEntireReadAsyncReturnsNewData() Assert.NotEqual(readResult, readResult2); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/4621")] + [Fact] public async Task ReadCanBeCancelledViaProvidedCancellationToken() { var pipeReader = new StreamPipeReader(new HangingStream()); @@ -533,6 +530,79 @@ public void SetOptionsToNullThrows() Assert.Throws(() => new StreamPipeReader(MemoryStream, null)); } + [Fact] + public async Task UseBothStreamAndPipeToRead() + { + Write(Encoding.ASCII.GetBytes(new string('a', 8))); + var buffer = new byte[4]; + + var res = MemoryStream.Read(buffer, 0, buffer.Length); + + Assert.Equal(4, res); + var readResult = await Reader.ReadAsync(); + + Assert.Equal(buffer, readResult.Buffer.ToArray()); + } + + [Fact] + public async Task UseStreamThenPipeToReadNoBytesLost() + { + CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); + var expectedString = "abcdefghijklmnopqrstuvwxyz"; + Write(Encoding.ASCII.GetBytes(expectedString)); + var buffer = new byte[1]; + var result = ""; + + for (var i = 0; i < 13; i++) + { + var res = MemoryStream.Read(buffer, 0, buffer.Length); + result += Encoding.ASCII.GetString(buffer); + var readResult = await Reader.ReadAsync(); + result += Encoding.ASCII.GetString(readResult.Buffer.ToArray()); + Reader.AdvanceTo(readResult.Buffer.End); + } + + Assert.Equal(expectedString, result); + } + + + [Fact] + public async Task UsePipeThenStreamToReadNoBytesLost() + { + CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); + var expectedString = "abcdefghijklmnopqrstuvwxyz"; + Write(Encoding.ASCII.GetBytes(expectedString)); + var buffer = new byte[1]; + var result = ""; + + for (var i = 0; i < 13; i++) + { + var readResult = await Reader.ReadAsync(); + result += Encoding.ASCII.GetString(readResult.Buffer.ToArray()); + Reader.AdvanceTo(readResult.Buffer.End); + var res = MemoryStream.Read(buffer, 0, buffer.Length); + result += Encoding.ASCII.GetString(buffer); + } + + Assert.Equal(expectedString, result); + } + [Fact] + public async Task UseBothStreamAndPipeToReadWithoutAdvance_StreamIgnoresAdvance() + { + CreateReader(minimumSegmentSize: 4, minimumReadThreshold: 4); + Write(Encoding.ASCII.GetBytes("aaaabbbbccccdddd")); + var buffer = new byte[4]; + var res = MemoryStream.Read(buffer, 0, buffer.Length); + 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 + res = MemoryStream.Read(buffer, 0, buffer.Length); + + Assert.Equal(4, res); + Assert.Equal(Encoding.ASCII.GetBytes("cccc"), buffer); + } + private void CreateReader(int minimumSegmentSize = 16, int minimumReadThreshold = 4, MemoryPool memoryPool = null) { Reader = new StreamPipeReader(MemoryStream, @@ -566,13 +636,11 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, return await base.ReadAsync(buffer, offset, count, cancellationToken); } -#if NETCOREAPP2_2 public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { await Task.Yield(); return await base.ReadAsync(buffer, cancellationToken); } -#endif } } } diff --git a/src/Http/Http/test/StreamPipeWriterTests.cs b/src/Http/Http/test/StreamPipeWriterTests.cs index e9bee842a14b..0fb628bca0c8 100644 --- a/src/Http/Http/test/StreamPipeWriterTests.cs +++ b/src/Http/Http/test/StreamPipeWriterTests.cs @@ -269,6 +269,66 @@ public async Task CancelPendingFlushLostOfCancellationsNoDataLost() Assert.Equal(16 * 10 * 2, Read().Length); } + [Fact] + public async Task UseBothStreamAndPipeToWrite() + { + var flushResult = await Writer.WriteAsync(Encoding.ASCII.GetBytes("aaaa")); + var buffer = Encoding.ASCII.GetBytes("cccc"); + MemoryStream.Write(buffer, 0, buffer.Length); + var result = Read(); + + Assert.Equal(Encoding.ASCII.GetBytes("aaaacccc"), result); + } + + [Fact] + public async Task UsePipeThenStreamToWriteMultipleTimes() + { + var expectedMemory = new Memory(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz")); + var buffer = new byte[1]; + + for (var i = 0; i < 13; i++) + { + MemoryStream.Write(expectedMemory.Slice(i * 2, 1).Span); + await Writer.WriteAsync(expectedMemory.Slice(i * 2 + 1, 1)); + } + var result = Read(); + Assert.Equal(expectedMemory.ToArray(), result); + } + + [Fact] + public async Task UseStreamThenPipeToWriteMultipleTimes() + { + var expectedMemory = new Memory(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz")); + var buffer = new byte[1]; + + for (var i = 0; i < 13; i++) + { + await Writer.WriteAsync(expectedMemory.Slice(i * 2, 1)); + MemoryStream.Write(expectedMemory.Slice(i * 2 + 1, 1).Span); + } + var result = Read(); + Assert.Equal(expectedMemory.ToArray(), result); + } + + [Fact] + public async Task UseBothStreamAndPipeToWriteWithGetMemoryAndFlush() + { + var cBuffer = Encoding.ASCII.GetBytes("cccc"); + var aBuffer = Encoding.ASCII.GetBytes("aaaa"); + var memory = Writer.GetMemory(); + + MemoryStream.Write(aBuffer, 0, aBuffer.Length); + + cBuffer.CopyTo(memory); + + Writer.Advance(cBuffer.Length); + await Writer.FlushAsync(); + + var result = Read(); + + Assert.Equal(Encoding.ASCII.GetBytes("aaaacccc"), result); + } + private async Task CheckWriteIsNotCanceled() { var flushResult = await Writer.WriteAsync(Encoding.ASCII.GetBytes("data")); @@ -291,7 +351,6 @@ private void CheckCanceledFlush() internal class HangingStream : MemoryStream { - public HangingStream() { } @@ -311,13 +370,11 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, await Task.Delay(30000, cancellationToken); return 0; } -#if NETCOREAPP2_2 public override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) { await Task.Delay(30000, cancellationToken); return 0; } -#endif } internal class SingleWriteStream : MemoryStream @@ -326,8 +383,6 @@ internal class SingleWriteStream : MemoryStream public bool AllowAllWrites { get; set; } - -#if NETCOREAPP2_2 public override async ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) { try @@ -346,7 +401,6 @@ public override async ValueTask WriteAsync(ReadOnlyMemory source, Cancella _shouldNextWriteFail = !_shouldNextWriteFail; } } -#endif public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { From 42c4d085f143fe0d00599b83b6e09f8985af88e6 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 4 Jan 2019 12:06:38 -0800 Subject: [PATCH 02/14] Minor nits --- src/Http/Http/src/Features/RequestBodyPipeFeature.cs | 3 +-- src/Http/Http/src/Features/ResponseBodyPipeFeature.cs | 3 +-- src/Http/Http/test/StreamPipeReaderTests.cs | 6 +++--- src/Http/Http/test/StreamPipeWriterTests.cs | 5 +++++ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index d503c571e296..0107b5d716ec 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -47,8 +47,7 @@ public PipeReader PipeReader } else { - // TODO set the Request body - // HttpRequestFeature.Body = new PipeStreamReader(); + // TODO set the Response body to 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 ef326756dc56..d1508408bfea 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -47,8 +47,7 @@ public PipeWriter PipeWriter } else { - // TODO set the Response body - // HttpResponseFeature.Body = new PipeStreamWriter(); + // TODO set the Response body to adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 } } } diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs index 55c7cf27f30a..634933759f4d 100644 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ b/src/Http/Http/test/StreamPipeReaderTests.cs @@ -148,7 +148,7 @@ public async Task ReadExamineEntireReadAsyncReturnsNewData() Assert.NotEqual(readResult, readResult2); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/4621")] public async Task ReadCanBeCancelledViaProvidedCancellationToken() { var pipeReader = new StreamPipeReader(new HangingStream()); @@ -565,7 +565,6 @@ public async Task UseStreamThenPipeToReadNoBytesLost() Assert.Equal(expectedString, result); } - [Fact] public async Task UsePipeThenStreamToReadNoBytesLost() { @@ -635,12 +634,13 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, await Task.Yield(); return await base.ReadAsync(buffer, offset, count, cancellationToken); } - +#if NETCOREAPP3_0 public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { await Task.Yield(); return await base.ReadAsync(buffer, cancellationToken); } +#endif } } } diff --git a/src/Http/Http/test/StreamPipeWriterTests.cs b/src/Http/Http/test/StreamPipeWriterTests.cs index 0fb628bca0c8..2d105c0908c4 100644 --- a/src/Http/Http/test/StreamPipeWriterTests.cs +++ b/src/Http/Http/test/StreamPipeWriterTests.cs @@ -370,11 +370,14 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, await Task.Delay(30000, cancellationToken); return 0; } + +#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 @@ -383,6 +386,7 @@ internal class SingleWriteStream : MemoryStream public bool AllowAllWrites { get; set; } + #if NETCOREAPP3_0 public override async ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) { try @@ -401,6 +405,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory source, Cancella _shouldNextWriteFail = !_shouldNextWriteFail; } } +#endif public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { From 43c958225f28d8637c7ad69190ac57ffe22dbd35 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 4 Jan 2019 15:39:13 -0800 Subject: [PATCH 03/14] fb --- src/Http/Http/src/Internal/DefaultHttpRequest.cs | 3 ++- src/Http/Http/src/StreamPipeReader.cs | 2 +- src/Http/Http/src/StreamPipeWriter.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs index c04f5573fafc..7bfdb651bb30 100644 --- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs +++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs @@ -167,7 +167,8 @@ public override RouteValueDictionary RouteValues set { RouteValuesFeature.RouteValues = value; } } - public override PipeReader BodyPipe { + public override PipeReader BodyPipe + { get { return RequestBodyPipeFeature.PipeReader; } set { RequestBodyPipeFeature.PipeReader = value; } } diff --git a/src/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs index 33919396b995..0fe6ca4f54de 100644 --- a/src/Http/Http/src/StreamPipeReader.cs +++ b/src/Http/Http/src/StreamPipeReader.cs @@ -73,7 +73,7 @@ public StreamPipeReader(Stream readingStream, StreamPipeReaderOptions options) /// /// Gets the inner stream that is being read from. /// - public Stream InnerStream { get { return _readingStream; } } + public Stream InnerStream =>_readingStream; /// public override void AdvanceTo(SequencePosition consumed) diff --git a/src/Http/Http/src/StreamPipeWriter.cs b/src/Http/Http/src/StreamPipeWriter.cs index ad6d2c3b5f3b..622b7613890b 100644 --- a/src/Http/Http/src/StreamPipeWriter.cs +++ b/src/Http/Http/src/StreamPipeWriter.cs @@ -67,7 +67,7 @@ public StreamPipeWriter(Stream writingStream, int minimumSegmentSize, MemoryPool /// /// Gets the inner stream that is being written to. /// - public Stream InnerStream { get { return _writingStream; } } + public Stream InnerStream => _writingStream; /// public override void Advance(int count) @@ -146,7 +146,7 @@ public override void OnReaderCompleted(Action callback, objec /// public override ValueTask FlushAsync(CancellationToken cancellationToken = default) { - if (_bytesWritten == 0) + if (== 0) { return new ValueTask(new FlushResult(isCanceled: false, IsCompletedOrThrow())); } From 50faea0610a1c5ada4ff1a0a85147ed2dd3395e2 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 4 Jan 2019 16:40:09 -0800 Subject: [PATCH 04/14] Feedback --- .../src/Features/RequestBodyPipeFeature.cs | 6 ++++-- .../src/Features/ResponseBodyPipeFeature.cs | 4 +++- src/Http/Http/src/StreamPipeWriter.cs | 4 ++-- .../Features/RequestBodyPipeFeatureTests.cs | 21 +++++++++++++++++++ .../Features/ResponseBodyPipeFeatureTests.cs | 19 +++++++++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index 0107b5d716ec..f641d5f81e83 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.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.IO; using System.IO.Pipelines; namespace Microsoft.AspNetCore.Http.Features @@ -31,7 +32,8 @@ public PipeReader PipeReader { get { - if (_pipeReader == null) + if (_pipeReader == null || + (_pipeReader is StreamPipeReader reader && !object.ReferenceEquals(reader.InnerStream, HttpRequestFeature.Body))) { _pipeReader = new StreamPipeReader(HttpRequestFeature.Body); } @@ -43,7 +45,7 @@ public PipeReader PipeReader _pipeReader = value; if (_pipeReader == null) { - HttpRequestFeature.Body = null; + HttpRequestFeature.Body = Stream.Null; } else { diff --git a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs index d1508408bfea..213e72110ae8 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -31,7 +31,9 @@ public PipeWriter PipeWriter { get { - if (_pipeWriter == null) + if (_pipeWriter == null || + // If the Response.Body has been updated, recreate the pipeWriter + (_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, HttpResponseFeature.Body))) { _pipeWriter = new StreamPipeWriter(HttpResponseFeature.Body); } diff --git a/src/Http/Http/src/StreamPipeWriter.cs b/src/Http/Http/src/StreamPipeWriter.cs index 622b7613890b..6926f1e9b9c8 100644 --- a/src/Http/Http/src/StreamPipeWriter.cs +++ b/src/Http/Http/src/StreamPipeWriter.cs @@ -65,7 +65,7 @@ public StreamPipeWriter(Stream writingStream, int minimumSegmentSize, MemoryPool } /// - /// Gets the inner stream that is being written to. + /// Gets the inner stream that is being read from. /// public Stream InnerStream => _writingStream; @@ -146,7 +146,7 @@ public override void OnReaderCompleted(Action callback, objec /// public override ValueTask FlushAsync(CancellationToken cancellationToken = default) { - if (== 0) + if (_bytesWritten == 0) { return new ValueTask(new FlushResult(isCanceled: false, IsCompletedOrThrow())); } diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index 81c3c1db9f9b..25e01ed08fb9 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -40,5 +40,26 @@ public void RequestBodySetPipeReaderReturnsSameValue() Assert.Equal(pipeReader, provider.PipeReader); } + + + [Fact] + public void RequestBodyGetPipeReaderAfterSettingBodyTwice() + { + var features = new FeatureCollection(); + var request = new HttpRequestFeature(); + var expectedStream = new MemoryStream(); + request.Body = new MemoryStream(); + features[typeof(IHttpRequestFeature)] = request; + + var provider = new RequestBodyPipeFeature(features); + + var pipeBody = provider.PipeReader; + // Requery the PipeReader after setting the body again. + request.Body = expectedStream; + pipeBody = provider.PipeReader; + + Assert.True(pipeBody is StreamPipeReader); + Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); + } } } diff --git a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs index d713d9a1fb05..251bf4737007 100644 --- a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs @@ -40,5 +40,24 @@ public void ResponseBodySetPipeReaderReturnsSameValue() Assert.Equal(pipeWriter, provider.PipeWriter); } + + [Fact] + public void ResponseBodyGetPipeWriterAfterSettingBodyTwice() + { + var features = new FeatureCollection(); + var response = new HttpResponseFeature(); + var expectedStream = new MemoryStream(); + response.Body = new MemoryStream(); + features[typeof(IHttpResponseFeature)] = response; + + var provider = new ResponseBodyPipeFeature(features); + + var pipeBody = provider.PipeWriter; + response.Body = expectedStream; + pipeBody = provider.PipeWriter; + + Assert.True(pipeBody is StreamPipeWriter); + Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); + } } } From 1e7a3802a25783dd7c88e9c1a9c5dba50f325a20 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 7 Jan 2019 11:12:27 -0800 Subject: [PATCH 05/14] Feedback --- .../src/Microsoft.AspNetCore.Http.Features.csproj | 3 +-- src/Http/Http/src/Features/RequestBodyPipeFeature.cs | 11 ++--------- src/Http/Http/src/Features/ResponseBodyPipeFeature.cs | 10 +--------- .../Http/test/Internal/DefaultHttpRequestTests.cs | 5 ++--- .../Http/test/Internal/DefaultHttpResponseTests.cs | 5 ++--- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj index 90f707c6437b..f1cf8b0b9eee 100644 --- a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj +++ b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core HTTP feature interface definitions. @@ -11,7 +11,6 @@ - diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index f641d5f81e83..a881884a103b 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -42,15 +42,8 @@ public PipeReader PipeReader } set { - _pipeReader = value; - if (_pipeReader == null) - { - HttpRequestFeature.Body = Stream.Null; - } - else - { - // TODO set the Response body to adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 - } + _pipeReader = value ?? throw new ArgumentNullException(nameof(value)); + // TODO set the Response body to 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 213e72110ae8..5f10fb5f5887 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -42,15 +42,7 @@ public PipeWriter PipeWriter } set { - _pipeWriter = value; - if (_pipeWriter == null) - { - HttpResponseFeature.Body = Stream.Null; - } - else - { - // TODO set the Response body to adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 - } + _pipeWriter = value ?? throw new ArgumentNullException(nameof(value)); } } } diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs index 5def8575ae07..5bdc9b14ce8f 100644 --- a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs @@ -275,11 +275,10 @@ public void BodyPipe_WrapsStream() } [Fact] - public void BodyPipe_NullOutBody() + public void BodyPipe_ThrowsWhenSettingNull() { var context = new DefaultHttpContext(); - context.Request.BodyPipe = null; - Assert.Null(context.Request.Body); + Assert.Throws(() => context.Request.BodyPipe = null); } private class CustomRouteValuesFeature : IRouteValuesFeature diff --git a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs index c25455f1aa7d..18c85701ec4f 100644 --- a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs @@ -93,11 +93,10 @@ public void BodyPipe_WrapsStream() } [Fact] - public void BodyPipe_NullOutBody() + public void BodyPipe_ThrowsWhenSettingNull() { var context = new DefaultHttpContext(); - context.Response.BodyPipe = null; - Assert.Equal(Stream.Null, context.Response.Body); + Assert.Throws(() => context.Response.BodyPipe = null); } private static HttpResponse CreateResponse(IHeaderDictionary headers) From a9929a0f7f826565bf49ddab756143754f776bf2 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 7 Jan 2019 11:41:37 -0800 Subject: [PATCH 06/14] Feedback --- .../Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj | 1 + src/Http/Http/src/StreamPipeReader.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj index f1cf8b0b9eee..9b2b7f8f15d9 100644 --- a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj +++ b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs index 0fe6ca4f54de..3b9619021798 100644 --- a/src/Http/Http/src/StreamPipeReader.cs +++ b/src/Http/Http/src/StreamPipeReader.cs @@ -73,7 +73,7 @@ public StreamPipeReader(Stream readingStream, StreamPipeReaderOptions options) /// /// Gets the inner stream that is being read from. /// - public Stream InnerStream =>_readingStream; + public Stream InnerStream => _readingStream; /// public override void AdvanceTo(SequencePosition consumed) From 158c6a32dc9bc4058441322694a2d8d6bd6ad734 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 7 Jan 2019 13:50:31 -0800 Subject: [PATCH 07/14] Feedback --- src/Http/Http.Abstractions/src/HttpRequest.cs | 2 +- src/Http/Http.Features/src/IRequestBodyPipeFeature.cs | 6 +++--- src/Http/Http.Features/src/IResponseBodyPipeFeature.cs | 2 +- src/Http/Http/src/Features/RequestBodyPipeFeature.cs | 4 ++-- src/Http/Http/src/Features/ResponseBodyPipeFeature.cs | 2 +- src/Http/Http/src/Internal/DefaultHttpRequest.cs | 4 ++-- src/Http/Http/src/Internal/DefaultHttpResponse.cs | 4 ++-- .../Http/test/Features/RequestBodyPipeFeatureTests.cs | 10 +++++----- .../Http/test/Features/ResponseBodyPipeFeatureTests.cs | 10 +++++----- src/Http/Http/test/StreamPipeReaderTests.cs | 1 + 10 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/Http/Http.Abstractions/src/HttpRequest.cs b/src/Http/Http.Abstractions/src/HttpRequest.cs index 6c7bd52372c0..5f32c8c8d39e 100644 --- a/src/Http/Http.Abstractions/src/HttpRequest.cs +++ b/src/Http/Http.Abstractions/src/HttpRequest.cs @@ -104,7 +104,7 @@ public abstract class HttpRequest public abstract Stream Body { get; set; } /// - /// Gets or sets the request body pipe + /// Gets or sets the request body pipe . /// public abstract PipeReader BodyPipe { get; set; } diff --git a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs index d2ea00745fbb..0c996ff6917d 100644 --- a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs +++ b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs @@ -6,13 +6,13 @@ namespace Microsoft.AspNetCore.Http.Features { /// - /// Represents the HttpRequestBody as a PipeReader + /// Represents the HttpRequestBody as a PipeReader. /// public interface IRequestBodyPipeFeature { /// - /// A representing the request body, if any. + /// A representing the request body, if any. /// - PipeReader PipeReader { get; set; } + PipeReader RequestBodyPipe { get; set; } } } diff --git a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs index cf129096ef02..3cbdd888e284 100644 --- a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs +++ b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs @@ -13,6 +13,6 @@ public interface IResponseBodyPipeFeature /// /// A representing the response body, if any. /// - PipeWriter PipeWriter { get; set; } + PipeWriter ResponseBodyPipe { get; set; } } } diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index a881884a103b..c089a94cc79a 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -28,7 +28,7 @@ public RequestBodyPipeFeature(IFeatureCollection features) private IHttpRequestFeature HttpRequestFeature => _features.Fetch(ref _features.Cache, _nullRequestFeature); - public PipeReader PipeReader + public PipeReader RequestBodyPipe { get { @@ -43,7 +43,7 @@ public PipeReader PipeReader set { _pipeReader = value ?? throw new ArgumentNullException(nameof(value)); - // TODO set the Response body to adapted pipe https://github.com/aspnet/AspNetCore/issues/3971 + // 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 5f10fb5f5887..db5daa334c2b 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -27,7 +27,7 @@ public ResponseBodyPipeFeature(IFeatureCollection features) private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, _nullRequestFeature); - public PipeWriter PipeWriter + public PipeWriter ResponseBodyPipe { get { diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs index 7bfdb651bb30..5f2314c80797 100644 --- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs +++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs @@ -169,8 +169,8 @@ public override RouteValueDictionary RouteValues public override PipeReader BodyPipe { - get { return RequestBodyPipeFeature.PipeReader; } - set { RequestBodyPipeFeature.PipeReader = value; } + get { return RequestBodyPipeFeature.RequestBodyPipe; } + set { RequestBodyPipeFeature.RequestBodyPipe = value; } } struct FeatureInterfaces diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs index 3ec814038822..fb6002dcb183 100644 --- a/src/Http/Http/src/Internal/DefaultHttpResponse.cs +++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs @@ -102,8 +102,8 @@ public override bool HasStarted public override PipeWriter BodyPipe { - get { return ResponseBodyPipeFeature.PipeWriter; } - set { ResponseBodyPipeFeature.PipeWriter = value; } + get { return ResponseBodyPipeFeature.ResponseBodyPipe; } + set { ResponseBodyPipeFeature.ResponseBodyPipe = value; } } public override void OnStarting(Func callback, object state) diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index 25e01ed08fb9..f0db5a218873 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -20,7 +20,7 @@ public void RequestBodyReturnsStreamPipeReader() var provider = new RequestBodyPipeFeature(features); - var pipeBody = provider.PipeReader; + var pipeBody = provider.RequestBodyPipe; Assert.True(pipeBody is StreamPipeReader); Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); @@ -36,9 +36,9 @@ public void RequestBodySetPipeReaderReturnsSameValue() var provider = new RequestBodyPipeFeature(features); var pipeReader = new Pipe().Reader; - provider.PipeReader = pipeReader; + provider.RequestBodyPipe = pipeReader; - Assert.Equal(pipeReader, provider.PipeReader); + Assert.Equal(pipeReader, provider.RequestBodyPipe); } @@ -53,10 +53,10 @@ public void RequestBodyGetPipeReaderAfterSettingBodyTwice() var provider = new RequestBodyPipeFeature(features); - var pipeBody = provider.PipeReader; + var pipeBody = provider.RequestBodyPipe; // Requery the PipeReader after setting the body again. request.Body = expectedStream; - pipeBody = provider.PipeReader; + pipeBody = provider.RequestBodyPipe; Assert.True(pipeBody is StreamPipeReader); Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); diff --git a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs index 251bf4737007..4caec99c0101 100644 --- a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs @@ -20,7 +20,7 @@ public void ResponseBodyReturnsStreamPipeReader() var provider = new ResponseBodyPipeFeature(features); - var pipeBody = provider.PipeWriter; + var pipeBody = provider.ResponseBodyPipe; Assert.True(pipeBody is StreamPipeWriter); Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); @@ -36,9 +36,9 @@ public void ResponseBodySetPipeReaderReturnsSameValue() var provider = new ResponseBodyPipeFeature(features); var pipeWriter = new Pipe().Writer; - provider.PipeWriter = pipeWriter; + provider.ResponseBodyPipe = pipeWriter; - Assert.Equal(pipeWriter, provider.PipeWriter); + Assert.Equal(pipeWriter, provider.ResponseBodyPipe); } [Fact] @@ -52,9 +52,9 @@ public void ResponseBodyGetPipeWriterAfterSettingBodyTwice() var provider = new ResponseBodyPipeFeature(features); - var pipeBody = provider.PipeWriter; + var pipeBody = provider.ResponseBodyPipe; response.Body = expectedStream; - pipeBody = provider.PipeWriter; + pipeBody = provider.ResponseBodyPipe; Assert.True(pipeBody is StreamPipeWriter); Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream); diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs index 634933759f4d..56a583797ec1 100644 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ b/src/Http/Http/test/StreamPipeReaderTests.cs @@ -634,6 +634,7 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, 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) { From 709a3718db4029f00e641696c577d0193fa1cf49 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 7 Jan 2019 14:28:07 -0800 Subject: [PATCH 08/14] Offline feedback --- .../src/Features/RequestBodyPipeFeature.cs | 24 +++++++---------- .../src/Features/ResponseBodyPipeFeature.cs | 22 +++++++--------- .../Http/src/Internal/DefaultHttpRequest.cs | 4 +-- .../Http/src/Internal/DefaultHttpResponse.cs | 4 +-- src/Http/Http/src/StreamPipeReader.cs | 2 +- .../Features/RequestBodyPipeFeatureTests.cs | 26 +++++++------------ .../Features/ResponseBodyPipeFeatureTests.cs | 25 +++++++----------- 7 files changed, 43 insertions(+), 64 deletions(-) diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index c089a94cc79a..b50b9d0bb2ce 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -2,40 +2,34 @@ // 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; namespace Microsoft.AspNetCore.Http.Features { public class RequestBodyPipeFeature : IRequestBodyPipeFeature { - // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624 - private readonly static Func _nullRequestFeature = f => null; - private PipeReader _pipeReader; - private FeatureReferences _features; + private HttpContext _context; - public RequestBodyPipeFeature(IFeatureCollection features) + public RequestBodyPipeFeature(HttpContext context) { - if (features == null) + if (context == null) { - throw new ArgumentNullException(nameof(features)); + throw new ArgumentNullException(nameof(context)); } - - _features = new FeatureReferences(features); + _context = context; } - private IHttpRequestFeature HttpRequestFeature => - _features.Fetch(ref _features.Cache, _nullRequestFeature); - public PipeReader RequestBodyPipe { get { if (_pipeReader == null || - (_pipeReader is StreamPipeReader reader && !object.ReferenceEquals(reader.InnerStream, HttpRequestFeature.Body))) + (_pipeReader is StreamPipeReader reader && !object.ReferenceEquals(reader.InnerStream, _context.Request.Body))) { - _pipeReader = new StreamPipeReader(HttpRequestFeature.Body); + var streamPipeReader = new StreamPipeReader(_context.Request.Body); + _pipeReader = streamPipeReader; + _context.Response.RegisterForDispose(streamPipeReader); } return _pipeReader; diff --git a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs index db5daa334c2b..f728fde8460e 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -9,33 +9,31 @@ namespace Microsoft.AspNetCore.Http.Features { public class ResponseBodyPipeFeature : IResponseBodyPipeFeature { - private readonly static Func _nullRequestFeature = f => null; - private PipeWriter _pipeWriter; - private FeatureReferences _features; + private HttpContext _context; - public ResponseBodyPipeFeature(IFeatureCollection features) + public ResponseBodyPipeFeature(HttpContext context) { - if (features == null) + if (context == null) { - throw new ArgumentNullException(nameof(features)); + throw new ArgumentNullException(nameof(context)); } - _features = new FeatureReferences(features); + _context = context; } - private IHttpResponseFeature HttpResponseFeature => - _features.Fetch(ref _features.Cache, _nullRequestFeature); - public PipeWriter ResponseBodyPipe { get { if (_pipeWriter == null || // If the Response.Body has been updated, recreate the pipeWriter - (_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, HttpResponseFeature.Body))) + (_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, _context.Response.Body))) { - _pipeWriter = new StreamPipeWriter(HttpResponseFeature.Body); + var streamPipeWriter = new StreamPipeWriter(_context.Response.Body); + _pipeWriter = streamPipeWriter; + _context.Response.RegisterForDispose(streamPipeWriter); + } return _pipeWriter; diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs index 5f2314c80797..4803942b9351 100644 --- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs +++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs @@ -20,7 +20,7 @@ public class DefaultHttpRequest : HttpRequest private readonly static Func _newFormFeature = r => new FormFeature(r); private readonly static Func _newRequestCookiesFeature = f => new RequestCookiesFeature(f); private readonly static Func _newRouteValuesFeature = f => new RouteValuesFeature(); - private readonly static Func _newRequestBodyPipeFeature = f => new RequestBodyPipeFeature(f); + private readonly static Func _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context); private HttpContext _context; private FeatureReferences _features; @@ -60,7 +60,7 @@ public virtual void Uninitialize() _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature); private IRequestBodyPipeFeature RequestBodyPipeFeature => - _features.Fetch(ref _features.Cache.BodyPipe, _newRequestBodyPipeFeature); + _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newRequestBodyPipeFeature); public override PathString PathBase { diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs index fb6002dcb183..b7ac14c25ae2 100644 --- a/src/Http/Http/src/Internal/DefaultHttpResponse.cs +++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs @@ -15,7 +15,7 @@ public class DefaultHttpResponse : HttpResponse // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func _nullResponseFeature = f => null; private readonly static Func _newResponseCookiesFeature = f => new ResponseCookiesFeature(f); - private readonly static Func _newResponseBodyPipeFeature = f => new ResponseBodyPipeFeature(f); + private readonly static Func _newResponseBodyPipeFeature = context => new ResponseBodyPipeFeature(context); private HttpContext _context; private FeatureReferences _features; @@ -44,7 +44,7 @@ public virtual void Uninitialize() _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature); private IResponseBodyPipeFeature ResponseBodyPipeFeature => - _features.Fetch(ref _features.Cache.BodyPipe, _newResponseBodyPipeFeature); + _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newResponseBodyPipeFeature); public override HttpContext HttpContext { get { return _context; } } diff --git a/src/Http/Http/src/StreamPipeReader.cs b/src/Http/Http/src/StreamPipeReader.cs index 3b9619021798..9d9e64caca67 100644 --- a/src/Http/Http/src/StreamPipeReader.cs +++ b/src/Http/Http/src/StreamPipeReader.cs @@ -17,7 +17,7 @@ namespace System.IO.Pipelines /// /// Implements PipeReader using an underlying stream. /// - public class StreamPipeReader : PipeReader + public class StreamPipeReader : PipeReader, IDisposable { private readonly int _minimumSegmentSize; private readonly int _minimumReadThreshold; diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index f0db5a218873..1fc582094fd4 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -12,13 +12,11 @@ public class RequestBodyPipeFeatureTests [Fact] public void RequestBodyReturnsStreamPipeReader() { - var features = new FeatureCollection(); - var request = new HttpRequestFeature(); + var context = new DefaultHttpContext(); var expectedStream = new MemoryStream(); - request.Body = expectedStream; - features[typeof(IHttpRequestFeature)] = request; + context.Request.Body = expectedStream; - var provider = new RequestBodyPipeFeature(features); + var provider = new RequestBodyPipeFeature(context); var pipeBody = provider.RequestBodyPipe; @@ -29,11 +27,9 @@ public void RequestBodyReturnsStreamPipeReader() [Fact] public void RequestBodySetPipeReaderReturnsSameValue() { - var features = new FeatureCollection(); - var request = new HttpRequestFeature(); - features[typeof(IHttpRequestFeature)] = request; + var context = new DefaultHttpContext(); - var provider = new RequestBodyPipeFeature(features); + var provider = new RequestBodyPipeFeature(context); var pipeReader = new Pipe().Reader; provider.RequestBodyPipe = pipeReader; @@ -41,21 +37,19 @@ public void RequestBodySetPipeReaderReturnsSameValue() Assert.Equal(pipeReader, provider.RequestBodyPipe); } - [Fact] public void RequestBodyGetPipeReaderAfterSettingBodyTwice() { - var features = new FeatureCollection(); - var request = new HttpRequestFeature(); + var context = new DefaultHttpContext(); + var expectedStream = new MemoryStream(); - request.Body = new MemoryStream(); - features[typeof(IHttpRequestFeature)] = request; + context.Request.Body = new MemoryStream(); - var provider = new RequestBodyPipeFeature(features); + var provider = new RequestBodyPipeFeature(context); var pipeBody = provider.RequestBodyPipe; // Requery the PipeReader after setting the body again. - request.Body = expectedStream; + context.Request.Body = expectedStream; pipeBody = provider.RequestBodyPipe; Assert.True(pipeBody is StreamPipeReader); diff --git a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs index 4caec99c0101..51aa7422201c 100644 --- a/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/ResponseBodyPipeFeatureTests.cs @@ -12,13 +12,11 @@ public class ResponseBodyPipeFeatureTests [Fact] public void ResponseBodyReturnsStreamPipeReader() { - var features = new FeatureCollection(); - var response = new HttpResponseFeature(); + var context = new DefaultHttpContext(); var expectedStream = new MemoryStream(); - response.Body = expectedStream; - features[typeof(IHttpResponseFeature)] = response; + context.Response.Body = expectedStream; - var provider = new ResponseBodyPipeFeature(features); + var provider = new ResponseBodyPipeFeature(context); var pipeBody = provider.ResponseBodyPipe; @@ -29,11 +27,8 @@ public void ResponseBodyReturnsStreamPipeReader() [Fact] public void ResponseBodySetPipeReaderReturnsSameValue() { - var features = new FeatureCollection(); - var response = new HttpResponseFeature(); - features[typeof(IHttpResponseFeature)] = response; - - var provider = new ResponseBodyPipeFeature(features); + var context = new DefaultHttpContext(); + var provider = new ResponseBodyPipeFeature(context); var pipeWriter = new Pipe().Writer; provider.ResponseBodyPipe = pipeWriter; @@ -44,16 +39,14 @@ public void ResponseBodySetPipeReaderReturnsSameValue() [Fact] public void ResponseBodyGetPipeWriterAfterSettingBodyTwice() { - var features = new FeatureCollection(); - var response = new HttpResponseFeature(); + var context = new DefaultHttpContext(); var expectedStream = new MemoryStream(); - response.Body = new MemoryStream(); - features[typeof(IHttpResponseFeature)] = response; + context.Response.Body = new MemoryStream(); - var provider = new ResponseBodyPipeFeature(features); + var provider = new ResponseBodyPipeFeature(context); var pipeBody = provider.ResponseBodyPipe; - response.Body = expectedStream; + context.Response.Body = expectedStream; pipeBody = provider.ResponseBodyPipe; Assert.True(pipeBody is StreamPipeWriter); From 5b7ee328a782c0779e139892f2f1e2929ea665d7 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 7 Jan 2019 14:42:40 -0800 Subject: [PATCH 09/14] Fix test --- .../UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Middleware/WebSockets/test/UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj b/src/Middleware/WebSockets/test/UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj index 428fc49b8f88..4e125127354d 100644 --- a/src/Middleware/WebSockets/test/UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj +++ b/src/Middleware/WebSockets/test/UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj @@ -5,8 +5,8 @@ - - + + From 4733d88d3fd328d9e20ceebcc37d8b8001be79cd Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 8 Jan 2019 08:29:33 -0800 Subject: [PATCH 10/14] Feedback --- .../src/IResponseBodyPipeFeature.cs | 1 + .../src/Features/RequestBodyPipeFeature.cs | 24 +++-- .../src/Features/ResponseBodyPipeFeature.cs | 26 ++++-- .../Features/RequestBodyPipeFeatureTests.cs | 91 ++++++++++++++++++- src/Http/Http/test/PipeTest.cs | 7 ++ src/Http/Http/test/StreamPipeReaderTests.cs | 77 +++++++++------- src/Http/Http/test/StreamPipeWriterTests.cs | 70 +++++++------- 7 files changed, 209 insertions(+), 87 deletions(-) diff --git a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs index 3cbdd888e284..dd8bb798a974 100644 --- a/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs +++ b/src/Http/Http.Features/src/IResponseBodyPipeFeature.cs @@ -1,6 +1,7 @@ // 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; namespace Microsoft.AspNetCore.Http.Features diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index b50b9d0bb2ce..ef3b4e245e85 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Http.Features { public class RequestBodyPipeFeature : IRequestBodyPipeFeature { - private PipeReader _pipeReader; + private StreamPipeReader _internalPipeReader; + private PipeReader _userSetPipeReader; private HttpContext _context; public RequestBodyPipeFeature(HttpContext context) @@ -24,19 +25,26 @@ public PipeReader RequestBodyPipe { get { - if (_pipeReader == null || - (_pipeReader is StreamPipeReader reader && !object.ReferenceEquals(reader.InnerStream, _context.Request.Body))) + if (_userSetPipeReader != null) { - var streamPipeReader = new StreamPipeReader(_context.Request.Body); - _pipeReader = streamPipeReader; - _context.Response.RegisterForDispose(streamPipeReader); + return _userSetPipeReader; } - return _pipeReader; + if (_internalPipeReader == null) + { + _internalPipeReader = new StreamPipeReader(_context.Request.Body); + } + else if (!object.ReferenceEquals(_internalPipeReader.InnerStream, _context.Request.Body)) + { + _internalPipeReader = new StreamPipeReader(_context.Request.Body); + _context.Response.RegisterForDispose(_internalPipeReader); + } + + return _internalPipeReader; } set { - _pipeReader = value ?? throw new ArgumentNullException(nameof(value)); + _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 f728fde8460e..d4c3e99d026e 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -2,14 +2,14 @@ // 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; namespace Microsoft.AspNetCore.Http.Features { public class ResponseBodyPipeFeature : IResponseBodyPipeFeature { - private PipeWriter _pipeWriter; + private StreamPipeWriter _internalPipeWriter; + private PipeWriter _userSetPipeWriter; private HttpContext _context; public ResponseBodyPipeFeature(HttpContext context) @@ -26,21 +26,27 @@ public PipeWriter ResponseBodyPipe { get { - if (_pipeWriter == null || - // If the Response.Body has been updated, recreate the pipeWriter - (_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, _context.Response.Body))) + if (_userSetPipeWriter != null) { - var streamPipeWriter = new StreamPipeWriter(_context.Response.Body); - _pipeWriter = streamPipeWriter; - _context.Response.RegisterForDispose(streamPipeWriter); + return _userSetPipeWriter; + } + if (_internalPipeWriter == null) + { + var streamPipeWriter = new StreamPipeWriter(_context.Response.Body); + _internalPipeWriter = streamPipeWriter; + } + else if (!object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body)) + { + _internalPipeWriter = new StreamPipeWriter(_context.Response.Body); + _context.Response.RegisterForDispose(_internalPipeWriter); } - return _pipeWriter; + return _internalPipeWriter; } set { - _pipeWriter = value ?? throw new ArgumentNullException(nameof(value)); + _userSetPipeWriter = value ?? throw new ArgumentNullException(nameof(value)); } } } diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index 1fc582094fd4..a3ac7ebb6428 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -1,8 +1,12 @@ // 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.Tasks; using Xunit; namespace Microsoft.AspNetCore.Http.Features @@ -24,6 +28,16 @@ public void RequestBodyReturnsStreamPipeReader() Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); } + [Fact] + public async Task RequestBodyReadCanWorkWithPipe() + { + var expectedString = "abcdef"; + var provider = InitializeFeatureWithData(expectedString); + + var data = await provider.RequestBodyPipe.ReadAsync(); + Assert.Equal(expectedString, GetStringFromReadResult(data)); + } + [Fact] public void RequestBodySetPipeReaderReturnsSameValue() { @@ -37,23 +51,98 @@ public void RequestBodySetPipeReaderReturnsSameValue() Assert.Equal(pipeReader, provider.RequestBodyPipe); } + [Fact] + public void RequestBodySetPipeReadReturnsUserSetValueAlways() + { + var context = new DefaultHttpContext(); + + var provider = new RequestBodyPipeFeature(context); + + var expectedPipeReader = new Pipe().Reader; + provider.RequestBodyPipe = expectedPipeReader; + + // Because the user set the RequestBodyPipe, this will return the user set pipeReader + context.Request.Body = new MemoryStream(); + + Assert.Equal(expectedPipeReader, provider.RequestBodyPipe); + } + + [Fact] + public async Task RequestBodyDoesNotAffectUserSetPipe() + { + var expectedString = "abcdef"; + var provider = InitializeFeatureWithData("hahaha"); + provider.RequestBodyPipe = await GetPipeReaderWithData(expectedString); + + var data = await provider.RequestBodyPipe.ReadAsync(); + Assert.Equal(expectedString, GetStringFromReadResult(data)); + } + [Fact] public void RequestBodyGetPipeReaderAfterSettingBodyTwice() { var context = new DefaultHttpContext(); - var expectedStream = new MemoryStream(); context.Request.Body = new MemoryStream(); var provider = new RequestBodyPipeFeature(context); var pipeBody = provider.RequestBodyPipe; + // Requery the PipeReader after setting the body again. + var expectedStream = new MemoryStream(); context.Request.Body = expectedStream; pipeBody = provider.RequestBodyPipe; Assert.True(pipeBody is StreamPipeReader); Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream); } + + [Fact] + public async Task RequestBodyGetsDataFromSecondStream() + { + var context = new DefaultHttpContext(); + context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes("hahaha")); + var provider = new RequestBodyPipeFeature(context); + var _ = provider.RequestBodyPipe; + + var expectedString = "abcdef"; + context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes(expectedString)); + var data = await provider.RequestBodyPipe.ReadAsync(); + Assert.Equal(expectedString, GetStringFromReadResult(data)); + } + + [Fact] + public void RequestBodyCheckObjectRegisteredForDispose() + { + var context = new DefaultHttpContext(); + var expectedStream = new MemoryStream(); + context.Request.Body = expectedStream; + + var provider = new RequestBodyPipeFeature(context); + + var pipeBody = provider.RequestBodyPipe; + } + + 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; + } + // Check for double set + // Check for if object is disposed or not (tricky) } } diff --git a/src/Http/Http/test/PipeTest.cs b/src/Http/Http/test/PipeTest.cs index 1aa2e5e06ac2..01801b95187c 100644 --- a/src/Http/Http/test/PipeTest.cs +++ b/src/Http/Http/test/PipeTest.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.IO.Pipelines; +using System.Text; namespace System.IO.Pipelines.Tests { @@ -38,6 +39,12 @@ public byte[] Read() return ReadWithoutFlush(); } + public string ReadAsString() + { + Writer.FlushAsync().GetAwaiter().GetResult(); + return Encoding.ASCII.GetString(ReadWithoutFlush()); + } + public void Write(byte[] data) { MemoryStream.Write(data, 0, data.Length); diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs index 56a583797ec1..07ce9efd0afb 100644 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ b/src/Http/Http/test/StreamPipeReaderTests.cs @@ -531,14 +531,12 @@ public void SetOptionsToNullThrows() } [Fact] - public async Task UseBothStreamAndPipeToRead() + public async Task UseBothStreamAndPipeToReadConfirmSameSize() { - Write(Encoding.ASCII.GetBytes(new string('a', 8))); + Write(new byte[8]); var buffer = new byte[4]; - var res = MemoryStream.Read(buffer, 0, buffer.Length); - - Assert.Equal(4, res); + MemoryStream.Read(buffer, 0, buffer.Length); var readResult = await Reader.ReadAsync(); Assert.Equal(buffer, readResult.Buffer.ToArray()); @@ -548,58 +546,73 @@ public async Task UseBothStreamAndPipeToRead() public async Task UseStreamThenPipeToReadNoBytesLost() { CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); - var expectedString = "abcdefghijklmnopqrstuvwxyz"; - Write(Encoding.ASCII.GetBytes(expectedString)); + + var expectedString = WriteString("abcdef"); + var accumulatedResult = ""; var buffer = new byte[1]; - var result = ""; - for (var i = 0; i < 13; i++) + for (var i = 0; i < expectedString.Length / 2; i++) { - var res = MemoryStream.Read(buffer, 0, buffer.Length); - result += Encoding.ASCII.GetString(buffer); - var readResult = await Reader.ReadAsync(); - result += Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Reader.AdvanceTo(readResult.Buffer.End); + // Read from stream then pipe to guarantee no bytes are lost. + accumulatedResult += ReadFromStreamAsString(buffer); + accumulatedResult += await ReadFromPipeAsString(); } - Assert.Equal(expectedString, result); + Assert.Equal(expectedString, accumulatedResult); } [Fact] public async Task UsePipeThenStreamToReadNoBytesLost() { CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1); - var expectedString = "abcdefghijklmnopqrstuvwxyz"; - Write(Encoding.ASCII.GetBytes(expectedString)); + + var expectedString = WriteString("abcdef"); + var accumulatedResult = ""; var buffer = new byte[1]; - var result = ""; - for (var i = 0; i < 13; i++) + for (var i = 0; i < expectedString.Length / 2; i++) { - var readResult = await Reader.ReadAsync(); - result += Encoding.ASCII.GetString(readResult.Buffer.ToArray()); - Reader.AdvanceTo(readResult.Buffer.End); - var res = MemoryStream.Read(buffer, 0, buffer.Length); - result += Encoding.ASCII.GetString(buffer); + // Read from pipe then stream to guarantee no bytes are lost. + accumulatedResult += await ReadFromPipeAsString(); + accumulatedResult += ReadFromStreamAsString(buffer); } - Assert.Equal(expectedString, result); + Assert.Equal(expectedString, accumulatedResult); } + [Fact] public async Task UseBothStreamAndPipeToReadWithoutAdvance_StreamIgnoresAdvance() { - CreateReader(minimumSegmentSize: 4, minimumReadThreshold: 4); - Write(Encoding.ASCII.GetBytes("aaaabbbbccccdddd")); - var buffer = new byte[4]; - var res = MemoryStream.Read(buffer, 0, buffer.Length); + 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 - res = MemoryStream.Read(buffer, 0, buffer.Length); + Assert.Equal("c", ReadFromStreamAsString(buffer)); + } + + private async Task ReadFromPipeAsString() + { + var readResult = await Reader.ReadAsync(); + var result = Encoding.ASCII.GetString(readResult.Buffer.ToArray()); + Reader.AdvanceTo(readResult.Buffer.End); + return result; + } - Assert.Equal(4, res); - Assert.Equal(Encoding.ASCII.GetBytes("cccc"), buffer); + private string ReadFromStreamAsString(byte[] buffer) + { + var res = MemoryStream.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) diff --git a/src/Http/Http/test/StreamPipeWriterTests.cs b/src/Http/Http/test/StreamPipeWriterTests.cs index 2d105c0908c4..f0f1d7916cbf 100644 --- a/src/Http/Http/test/StreamPipeWriterTests.cs +++ b/src/Http/Http/test/StreamPipeWriterTests.cs @@ -272,61 +272,59 @@ public async Task CancelPendingFlushLostOfCancellationsNoDataLost() [Fact] public async Task UseBothStreamAndPipeToWrite() { - var flushResult = await Writer.WriteAsync(Encoding.ASCII.GetBytes("aaaa")); - var buffer = Encoding.ASCII.GetBytes("cccc"); - MemoryStream.Write(buffer, 0, buffer.Length); - var result = Read(); + await WriteStringToPipeWriter("a"); + WriteStringToStream("c"); - Assert.Equal(Encoding.ASCII.GetBytes("aaaacccc"), result); + Assert.Equal("ac", ReadAsString()); } [Fact] public async Task UsePipeThenStreamToWriteMultipleTimes() { - var expectedMemory = new Memory(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz")); - var buffer = new byte[1]; - - for (var i = 0; i < 13; i++) + var expectedString = "abcdef"; + for (var i = 0; i < expectedString.Length; i++) { - MemoryStream.Write(expectedMemory.Slice(i * 2, 1).Span); - await Writer.WriteAsync(expectedMemory.Slice(i * 2 + 1, 1)); + if (i % 2 == 0) + { + WriteStringToStream(expectedString[i].ToString()); + } + else + { + await WriteStringToPipeWriter(expectedString[i].ToString()); + } } - var result = Read(); - Assert.Equal(expectedMemory.ToArray(), result); + + Assert.Equal(expectedString, ReadAsString()); } [Fact] public async Task UseStreamThenPipeToWriteMultipleTimes() { - var expectedMemory = new Memory(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz")); - var buffer = new byte[1]; - - for (var i = 0; i < 13; i++) + var expectedString = "abcdef"; + for (var i = 0; i < expectedString.Length; i++) { - await Writer.WriteAsync(expectedMemory.Slice(i * 2, 1)); - MemoryStream.Write(expectedMemory.Slice(i * 2 + 1, 1).Span); + if (i % 2 == 0) + { + await WriteStringToPipeWriter(expectedString[i].ToString()); + } + else + { + WriteStringToStream(expectedString[i].ToString()); + } } - var result = Read(); - Assert.Equal(expectedMemory.ToArray(), result); + + Assert.Equal(expectedString, ReadAsString()); } - [Fact] - public async Task UseBothStreamAndPipeToWriteWithGetMemoryAndFlush() + private void WriteStringToStream(string input) { - var cBuffer = Encoding.ASCII.GetBytes("cccc"); - var aBuffer = Encoding.ASCII.GetBytes("aaaa"); - var memory = Writer.GetMemory(); - - MemoryStream.Write(aBuffer, 0, aBuffer.Length); - - cBuffer.CopyTo(memory); - - Writer.Advance(cBuffer.Length); - await Writer.FlushAsync(); - - var result = Read(); + var buffer = Encoding.ASCII.GetBytes(input); + MemoryStream.Write(buffer, 0, buffer.Length); + } - Assert.Equal(Encoding.ASCII.GetBytes("aaaacccc"), result); + private async Task WriteStringToPipeWriter(string input) + { + await Writer.WriteAsync(Encoding.ASCII.GetBytes(input)); } private async Task CheckWriteIsNotCanceled() From c026818c61788a7b3531da7f2077ec4b60741f6e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 8 Jan 2019 08:33:50 -0800 Subject: [PATCH 11/14] small cleanup --- src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index a3ac7ebb6428..8db9ac72c0c4 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -122,6 +122,8 @@ public void RequestBodyCheckObjectRegisteredForDispose() var provider = new RequestBodyPipeFeature(context); var pipeBody = provider.RequestBodyPipe; + + // TODO. } private RequestBodyPipeFeature InitializeFeatureWithData(string input) @@ -142,7 +144,5 @@ private async Task GetPipeReaderWithData(string input) await pipe.Writer.WriteAsync(Encoding.ASCII.GetBytes(input)); return pipe.Reader; } - // Check for double set - // Check for if object is disposed or not (tricky) } } From 1c5eb9c911f0a6a683d79a83ebaffdd94552f720 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 8 Jan 2019 14:19:05 -0800 Subject: [PATCH 12/14] add build.cmd and feedback --- src/Http/Http/src/Features/RequestBodyPipeFeature.cs | 7 ++----- src/Http/Http/src/Features/ResponseBodyPipeFeature.cs | 10 +++------- src/Http/build.cmd | 3 +++ 3 files changed, 8 insertions(+), 12 deletions(-) create mode 100644 src/Http/build.cmd diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs index ef3b4e245e85..7f66fe9815a1 100644 --- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs @@ -30,11 +30,8 @@ public PipeReader RequestBodyPipe return _userSetPipeReader; } - if (_internalPipeReader == null) - { - _internalPipeReader = new StreamPipeReader(_context.Request.Body); - } - else if (!object.ReferenceEquals(_internalPipeReader.InnerStream, _context.Request.Body)) + if (_internalPipeReader == null || + !object.ReferenceEquals(_internalPipeReader.InnerStream, _context.Request.Body)) { _internalPipeReader = new StreamPipeReader(_context.Request.Body); _context.Response.RegisterForDispose(_internalPipeReader); diff --git a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs index d4c3e99d026e..b0d9c8ffc4c4 100644 --- a/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs +++ b/src/Http/Http/src/Features/ResponseBodyPipeFeature.cs @@ -18,7 +18,6 @@ public ResponseBodyPipeFeature(HttpContext context) { throw new ArgumentNullException(nameof(context)); } - _context = context; } @@ -31,12 +30,8 @@ public PipeWriter ResponseBodyPipe return _userSetPipeWriter; } - if (_internalPipeWriter == null) - { - var streamPipeWriter = new StreamPipeWriter(_context.Response.Body); - _internalPipeWriter = streamPipeWriter; - } - else if (!object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body)) + if (_internalPipeWriter == null || + !object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body)) { _internalPipeWriter = new StreamPipeWriter(_context.Response.Body); _context.Response.RegisterForDispose(_internalPipeWriter); @@ -47,6 +42,7 @@ public PipeWriter ResponseBodyPipe 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/build.cmd b/src/Http/build.cmd new file mode 100644 index 000000000000..033fe6f61468 --- /dev/null +++ b/src/Http/build.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SET RepoRoot=%~dp0..\.. +%RepoRoot%\build.cmd -projects %~dp0\**\*.*proj %* From fc2b31e20f75d9c6c78bb8557539b243df1d7fa0 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 8 Jan 2019 18:23:05 -0800 Subject: [PATCH 13/14] Final feedback --- .../test/Features/RequestBodyPipeFeatureTests.cs | 14 -------------- src/Http/Http/test/StreamPipeReaderTests.cs | 1 + src/Http/Http/test/StreamPipeWriterTests.cs | 4 +++- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs index 8db9ac72c0c4..542b12899fa8 100644 --- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs +++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs @@ -112,20 +112,6 @@ public async Task RequestBodyGetsDataFromSecondStream() Assert.Equal(expectedString, GetStringFromReadResult(data)); } - [Fact] - public void RequestBodyCheckObjectRegisteredForDispose() - { - var context = new DefaultHttpContext(); - var expectedStream = new MemoryStream(); - context.Request.Body = expectedStream; - - var provider = new RequestBodyPipeFeature(context); - - var pipeBody = provider.RequestBodyPipe; - - // TODO. - } - private RequestBodyPipeFeature InitializeFeatureWithData(string input) { var context = new DefaultHttpContext(); diff --git a/src/Http/Http/test/StreamPipeReaderTests.cs b/src/Http/Http/test/StreamPipeReaderTests.cs index 07ce9efd0afb..4c94bb6ce359 100644 --- a/src/Http/Http/test/StreamPipeReaderTests.cs +++ b/src/Http/Http/test/StreamPipeReaderTests.cs @@ -647,6 +647,7 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, 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) diff --git a/src/Http/Http/test/StreamPipeWriterTests.cs b/src/Http/Http/test/StreamPipeWriterTests.cs index f0f1d7916cbf..d51bca97726b 100644 --- a/src/Http/Http/test/StreamPipeWriterTests.cs +++ b/src/Http/Http/test/StreamPipeWriterTests.cs @@ -369,6 +369,7 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, 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) { @@ -384,7 +385,8 @@ internal class SingleWriteStream : MemoryStream public bool AllowAllWrites { get; set; } - #if NETCOREAPP3_0 + // Keeping as this code will eventually be ported to corefx +#if NETCOREAPP3_0 public override async ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) { try From 9f1969e779f2481613268082140208a1bc14fb00 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 8 Jan 2019 18:55:15 -0800 Subject: [PATCH 14/14] reacting to new http context --- src/Http/Http/src/Internal/ReusableHttpRequest.cs | 13 +++++++++++++ src/Http/Http/src/Internal/ReusableHttpResponse.cs | 12 +++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Http/Http/src/Internal/ReusableHttpRequest.cs b/src/Http/Http/src/Internal/ReusableHttpRequest.cs index f491acd41c23..2bbd1f56fa23 100644 --- a/src/Http/Http/src/Internal/ReusableHttpRequest.cs +++ b/src/Http/Http/src/Internal/ReusableHttpRequest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,6 +19,8 @@ public sealed class ReusableHttpRequest : HttpRequest private readonly static Func _newFormFeature = r => new FormFeature(r); private readonly static Func _newRequestCookiesFeature = f => new RequestCookiesFeature(f); private readonly static Func _newRouteValuesFeature = f => new RouteValuesFeature(); + private readonly static Func _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context); + private HttpContext _context; private FeatureReferences _features; @@ -56,6 +59,9 @@ public void Uninitialize() private IRouteValuesFeature RouteValuesFeature => _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature); + private IRequestBodyPipeFeature RequestBodyPipeFeature => + _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newRequestBodyPipeFeature); + public override PathString PathBase { get { return new PathString(HttpRequestFeature.PathBase); } @@ -161,6 +167,12 @@ public override RouteValueDictionary RouteValues set { RouteValuesFeature.RouteValues = value; } } + public override PipeReader BodyPipe + { + get { return RequestBodyPipeFeature.RequestBodyPipe; } + set { RequestBodyPipeFeature.RequestBodyPipe = value; } + } + struct FeatureInterfaces { public IHttpRequestFeature Request; @@ -168,6 +180,7 @@ struct FeatureInterfaces public IFormFeature Form; public IRequestCookiesFeature Cookies; public IRouteValuesFeature RouteValues; + public IRequestBodyPipeFeature BodyPipe; } } } diff --git a/src/Http/Http/src/Internal/ReusableHttpResponse.cs b/src/Http/Http/src/Internal/ReusableHttpResponse.cs index fd816351a82d..5a6e25fea5f2 100644 --- a/src/Http/Http/src/Internal/ReusableHttpResponse.cs +++ b/src/Http/Http/src/Internal/ReusableHttpResponse.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; @@ -13,6 +14,7 @@ public sealed class ReusableHttpResponse : HttpResponse // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624 private readonly static Func _nullResponseFeature = f => null; private readonly static Func _newResponseCookiesFeature = f => new ResponseCookiesFeature(f); + private readonly static Func _newResponseBodyPipeFeature = context => new ResponseBodyPipeFeature(context); private HttpContext _context; private FeatureReferences _features; @@ -39,7 +41,8 @@ public void Uninitialize() private IResponseCookiesFeature ResponseCookiesFeature => _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature); - + private IResponseBodyPipeFeature ResponseBodyPipeFeature => + _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newResponseBodyPipeFeature); public override HttpContext HttpContext { get { return _context; } } @@ -90,6 +93,12 @@ public override IResponseCookies Cookies get { return ResponseCookiesFeature.Cookies; } } + public override PipeWriter BodyPipe + { + get { return ResponseBodyPipeFeature.ResponseBodyPipe; } + set { ResponseBodyPipeFeature.ResponseBodyPipe = value; } + } + public override bool HasStarted { get { return HttpResponseFeature.HasStarted; } @@ -133,6 +142,7 @@ struct FeatureInterfaces { public IHttpResponseFeature Response; public IResponseCookiesFeature Cookies; + public IResponseBodyPipeFeature BodyPipe; } } }