ResponseBuffering middleware initial checkin.

Restrict buffer to reset. Add sample. Cleanup.
This commit is contained in:
Chris R 2015-07-31 16:30:31 -07:00
parent c88e7f5a44
commit c91cc89ee3
15 changed files with 870 additions and 1 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23018.0
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.HttpOverrides", "src\Microsoft.AspNet.HttpOverrides\Microsoft.AspNet.HttpOverrides.xproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
EndProject
@ -16,6 +16,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Buffering", "src\Microsoft.AspNet.Buffering\Microsoft.AspNet.Buffering.xproj", "{2363D0DD-A3BF-437E-9B64-B33AE132D875}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Buffering.Tests", "test\Microsoft.AspNet.Buffering.Tests\Microsoft.AspNet.Buffering.Tests.xproj", "{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9587FE9F-5A17-42C4-8021-E87F59CECB98}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseBufferingSample", "samples\ResponseBufferingSample\ResponseBufferingSample.xproj", "{E5C55B80-7827-40EB-B661-32B0E0E431CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -30,6 +38,18 @@ Global
{D6341B92-3416-4F11-8DF4-CB274296175F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6341B92-3416-4F11-8DF4-CB274296175F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6341B92-3416-4F11-8DF4-CB274296175F}.Release|Any CPU.Build.0 = Release|Any CPU
{2363D0DD-A3BF-437E-9B64-B33AE132D875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2363D0DD-A3BF-437E-9B64-B33AE132D875}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2363D0DD-A3BF-437E-9B64-B33AE132D875}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2363D0DD-A3BF-437E-9B64-B33AE132D875}.Release|Any CPU.Build.0 = Release|Any CPU
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB}.Release|Any CPU.Build.0 = Release|Any CPU
{E5C55B80-7827-40EB-B661-32B0E0E431CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5C55B80-7827-40EB-B661-32B0E0E431CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5C55B80-7827-40EB-B661-32B0E0E431CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5C55B80-7827-40EB-B661-32B0E0E431CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -37,5 +57,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{517308C3-B477-4B01-B461-CAB9C10B6928} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
{D6341B92-3416-4F11-8DF4-CB274296175F} = {8437B0F3-3894-4828-A945-A9187F37631D}
{2363D0DD-A3BF-437E-9B64-B33AE132D875} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB} = {8437B0F3-3894-4828-A945-A9187F37631D}
{E5C55B80-7827-40EB-B661-32B0E0E431CA} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,21 @@
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNET_ENV": "Development"
}
},
"web": {
"commandName": "web",
"launchBrowser": true,
"launchUrl": "http://localhost:5000/"
},
"kestrel": {
"commandName": "kestrel",
"launchBrowser": true,
"launchUrl": "http://localhost:5001/"
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>e5c55b80-7827-40eb-b661-32b0e0e431ca</ProjectGuid>
<RootNamespace>ResponseBufferingSample</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>46823</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,36 @@
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;
namespace ResponseBufferingSample
{
public class Startup
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
app.UseResponseBuffering();
app.Run(async (context) =>
{
// Write some stuff
context.Response.ContentType = "text/other";
await context.Response.WriteAsync("Hello World!");
// ... more work ...
// Something went wrong and we want to replace the response
context.Response.StatusCode = 200;
context.Response.Headers.Clear();
context.Response.Body.SetLength(0);
// Try again
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hi Bob!");
});
}
}
}

View File

@ -0,0 +1,30 @@
{
"webroot": "wwwroot",
"version": "1.0.0-*",
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Buffering": "1.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*"
},
"commands": {
"web": "Microsoft.AspNet.Server.WebListener --server.urls=http://localhost:5000",
"kestrel": "Kestrel --server.urls=http://localhost:5001"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"publishExclude": [
"node_modules",
"bower_components",
"**.xproj",
"**.user",
"**.vspscc"
],
"exclude": [
"wwwroot",
"node_modules",
"bower_components"
]
}

View File

@ -0,0 +1,218 @@
// 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.AspNet.Buffering
{
internal class BufferingWriteStream : Stream
{
private readonly Stream _innerStream;
private readonly MemoryStream _buffer = new MemoryStream();
private bool _isBuffering = true;
public BufferingWriteStream(Stream innerStream)
{
_innerStream = innerStream;
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return _isBuffering; }
}
public override bool CanWrite
{
get { return _innerStream.CanWrite; }
}
public override long Length
{
get
{
if (_isBuffering)
{
return _buffer.Length;
}
// May throw
return _innerStream.Length;
}
}
// Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported.
public override long Position
{
get
{
if (_isBuffering)
{
return _buffer.Position;
}
// May throw
return _innerStream.Position;
}
set
{
if (_isBuffering)
{
if (value != 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, nameof(Position) + " can only be set to 0.");
}
_buffer.Position = value;
_buffer.SetLength(value);
}
else
{
// May throw
_innerStream.Position = value;
}
}
}
// Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported.
public override void SetLength(long value)
{
if (_isBuffering)
{
if (value != 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, nameof(Length) + " can only be set to 0.");
}
_buffer.Position = value;
_buffer.SetLength(value);
}
else
{
// May throw
_innerStream.SetLength(value);
}
}
// Clear/Reset the buffer by setting Position, Seek, or SetLength to 0. Random access is not supported.
public override long Seek(long offset, SeekOrigin origin)
{
if (_isBuffering)
{
if (origin != SeekOrigin.Begin)
{
throw new ArgumentException(nameof(origin), nameof(Seek) + " can only be set to " + nameof(SeekOrigin.Begin) + ".");
}
if (offset != 0)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, nameof(Seek) + " can only be set to 0.");
}
_buffer.SetLength(offset);
return _buffer.Seek(offset, origin);
}
// Try the inner stream instead, but this will usually fail.
return _innerStream.Seek(offset, origin);
}
internal void DisableBuffering()
{
_isBuffering = false;
if (_buffer.Length > 0)
{
Flush();
}
}
internal Task DisableBufferingAsync(CancellationToken cancellationToken)
{
_isBuffering = false;
if (_buffer.Length > 0)
{
return FlushAsync(cancellationToken);
}
return Task.FromResult(0);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (_isBuffering)
{
_buffer.Write(buffer, offset, count);
}
else
{
_innerStream.Write(buffer, offset, count);
}
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (_isBuffering)
{
return _buffer.WriteAsync(buffer, offset, count, cancellationToken);
}
else
{
return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
}
}
#if !DNXCORE50
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
if (_isBuffering)
{
return _buffer.BeginWrite(buffer, offset, count, callback, state);
}
else
{
return _innerStream.BeginWrite(buffer, offset, count, callback, state);
}
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (_isBuffering)
{
_buffer.EndWrite(asyncResult);
}
else
{
_innerStream.EndWrite(asyncResult);
}
}
#endif
public override void Flush()
{
_isBuffering = false;
if (_buffer.Length > 0)
{
_buffer.Seek(0, SeekOrigin.Begin);
_buffer.CopyTo(_innerStream);
_buffer.Seek(0, SeekOrigin.Begin);
_buffer.SetLength(0);
}
_innerStream.Flush();
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
_isBuffering = false;
if (_buffer.Length > 0)
{
_buffer.Seek(0, SeekOrigin.Begin);
await _buffer.CopyToAsync(_innerStream, 1024 * 16, cancellationToken);
_buffer.Seek(0, SeekOrigin.Begin);
_buffer.SetLength(0);
}
await _innerStream.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("This Stream only supports Write operations.");
}
}
}

View File

@ -0,0 +1,30 @@
// 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 Microsoft.AspNet.Http.Features;
namespace Microsoft.AspNet.Buffering
{
internal class HttpBufferingFeature : IHttpBufferingFeature
{
private readonly BufferingWriteStream _buffer;
private readonly IHttpBufferingFeature _innerFeature;
internal HttpBufferingFeature(BufferingWriteStream buffer, IHttpBufferingFeature innerFeature)
{
_buffer = buffer;
_innerFeature = innerFeature;
}
public void DisableRequestBuffering()
{
_innerFeature?.DisableRequestBuffering();
}
public void DisableResponseBuffering()
{
_buffer.DisableBuffering();
_innerFeature?.DisableResponseBuffering();
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>2363d0dd-a3bf-437e-9b64-b33ae132d875</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Buffering</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,66 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
namespace Microsoft.AspNet.Buffering
{
public class ResponseBufferingMiddleware
{
private readonly RequestDelegate _next;
public ResponseBufferingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var originalResponseBody = httpContext.Response.Body;
// no-op if buffering is already available.
if (originalResponseBody.CanSeek)
{
await _next(httpContext);
return;
}
var originalBufferingFeature = httpContext.GetFeature<IHttpBufferingFeature>();
var originalSendFileFeature = httpContext.GetFeature<IHttpSendFileFeature>();
try
{
// Shim the response stream
var bufferStream = new BufferingWriteStream(originalResponseBody);
httpContext.Response.Body = bufferStream;
httpContext.SetFeature<IHttpBufferingFeature>(new HttpBufferingFeature(bufferStream, originalBufferingFeature));
if (originalSendFileFeature != null)
{
httpContext.SetFeature<IHttpSendFileFeature>(new SendFileFeatureWrapper(originalSendFileFeature, bufferStream));
}
await _next(httpContext);
// If we're still buffered, set the content-length header and flush the buffer.
// Only if the content-length header is not already set, and some content was buffered.
if (!httpContext.Response.HasStarted && bufferStream.CanSeek && bufferStream.Length > 0)
{
if (!httpContext.Response.ContentLength.HasValue)
{
httpContext.Response.ContentLength = bufferStream.Length;
}
await bufferStream.FlushAsync();
}
}
finally
{
// undo everything
httpContext.SetFeature(originalBufferingFeature);
httpContext.SetFeature(originalSendFileFeature);
httpContext.Response.Body = originalResponseBody;
}
}
}
}

View File

@ -0,0 +1,20 @@
// 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 Microsoft.AspNet.Buffering;
namespace Microsoft.AspNet.Builder
{
public static class ResponseBufferingMiddlewareExtensions
{
/// <summary>
/// Enables full buffering of response bodies. This can be disabled on a per request basis using IHttpBufferingFeature.
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseResponseBuffering(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ResponseBufferingMiddleware>();
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
namespace Microsoft.AspNet.Buffering
{
internal class SendFileFeatureWrapper : IHttpSendFileFeature
{
private readonly IHttpSendFileFeature _originalSendFileFeature;
private readonly BufferingWriteStream _bufferStream;
public SendFileFeatureWrapper(IHttpSendFileFeature originalSendFileFeature, BufferingWriteStream bufferStream)
{
_originalSendFileFeature = originalSendFileFeature;
_bufferStream = bufferStream;
}
// Flush and disable the buffer if anyone tries to call the SendFile feature.
public async Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{
await _bufferStream.DisableBufferingAsync(cancellation);
await _originalSendFileFeature.SendFileAsync(path, offset, length, cancellation);
}
}
}

View File

@ -0,0 +1,18 @@
{
"version": "1.0.0-*",
"description": "ASP.NET middleware for buffering response bodies.",
"repository": {
"type": "git",
"url": "git://github.com/aspnet/basicmiddleware"
},
"dependencies": {
"Microsoft.AspNet.Http.Abstractions": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": {
"dependencies": {
}
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f5f1d123-9c81-4a9e-8644-aa46b8e578fb</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Buffering.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,298 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.TestHost;
using Xunit;
namespace Microsoft.AspNet.Buffering.Tests
{
public class ResponseBufferingMiddlewareTests
{
[Fact]
public async Task BufferResponse_SetsContentLength()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
await context.Response.WriteAsync("Hello World");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
// Set automatically by buffer
IEnumerable<string> values;
Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values));
Assert.Equal("11", values.FirstOrDefault());
}
[Fact]
public async Task BufferResponseWithManualContentLength_NotReplaced()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
context.Response.ContentLength = 12;
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
await context.Response.WriteAsync("Hello World");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
IEnumerable<string> values;
Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values));
Assert.Equal("12", values.FirstOrDefault());
}
[Fact]
public async Task Seek_AllowsResttingBuffer()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
var body = context.Response.Body;
Assert.False(context.Response.HasStarted);
Assert.True(body.CanSeek);
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("Hello World");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
Assert.Equal(11, body.Position);
Assert.Equal(11, body.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => body.Seek(1, SeekOrigin.Begin));
Assert.Throws<ArgumentException>(() => body.Seek(0, SeekOrigin.Current));
Assert.Throws<ArgumentException>(() => body.Seek(0, SeekOrigin.End));
Assert.Equal(0, body.Seek(0, SeekOrigin.Begin));
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("12345");
Assert.Equal(5, body.Position);
Assert.Equal(5, body.Length);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("12345", await response.Content.ReadAsStringAsync());
// Set automatically by buffer
IEnumerable<string> values;
Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values));
Assert.Equal("5", values.FirstOrDefault());
}
[Fact]
public async Task SetPosition_AllowsResttingBuffer()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
var body = context.Response.Body;
Assert.False(context.Response.HasStarted);
Assert.True(body.CanSeek);
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("Hello World");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
Assert.Equal(11, body.Position);
Assert.Equal(11, body.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => body.Position = 1);
body.Position = 0;
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("12345");
Assert.Equal(5, body.Position);
Assert.Equal(5, body.Length);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("12345", await response.Content.ReadAsStringAsync());
// Set automatically by buffer
IEnumerable<string> values;
Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values));
Assert.Equal("5", values.FirstOrDefault());
}
[Fact]
public async Task SetLength_AllowsResttingBuffer()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
var body = context.Response.Body;
Assert.False(context.Response.HasStarted);
Assert.True(body.CanSeek);
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("Hello World");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
Assert.Equal(11, body.Position);
Assert.Equal(11, body.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => body.SetLength(1));
body.SetLength(0);
Assert.Equal(0, body.Position);
Assert.Equal(0, body.Length);
await context.Response.WriteAsync("12345");
Assert.Equal(5, body.Position);
Assert.Equal(5, body.Length);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("12345", await response.Content.ReadAsStringAsync());
// Set automatically by buffer
IEnumerable<string> values;
Assert.True(response.Content.Headers.TryGetValues("Content-Length", out values));
Assert.Equal("5", values.FirstOrDefault());
}
[Fact]
public async Task DisableBufferingViaFeature()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
var bufferingFeature = context.GetFeature<IHttpBufferingFeature>();
Assert.NotNull(bufferingFeature);
bufferingFeature.DisableResponseBuffering();
Assert.False(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
await context.Response.WriteAsync("Hello World");
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
IEnumerable<string> values;
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values));
}
[Fact]
public async Task DisableBufferingViaFeatureAfterFirstWrite_Flushes()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
await context.Response.WriteAsync("Hello");
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
var bufferingFeature = context.GetFeature<IHttpBufferingFeature>();
Assert.NotNull(bufferingFeature);
bufferingFeature.DisableResponseBuffering();
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
await context.Response.WriteAsync(" World");
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
IEnumerable<string> values;
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values));
}
[Fact]
public async Task FlushDisablesBuffering()
{
var server = TestServer.Create(app =>
{
app.UseResponseBuffering();
app.Run(async context =>
{
Assert.False(context.Response.HasStarted);
Assert.True(context.Response.Body.CanSeek);
context.Response.Body.Flush();
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
await context.Response.WriteAsync("Hello World");
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.Body.CanSeek);
});
});
var response = await server.CreateClient().GetAsync("");
response.EnsureSuccessStatusCode();
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
IEnumerable<string> values;
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out values));
}
}
}

View File

@ -0,0 +1,22 @@
{
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Buffering": "1.0.0-*",
"Microsoft.AspNet.TestHost": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": {
"test": "xunit.runner.aspnet"
},
"frameworks": {
"dnx451": { },
"dnxcore50": {
"dependencies": {
"System.Collections": "4.0.10-*",
"System.Linq": "4.0.0-*",
"System.Threading": "4.0.10-*"
}
}
}
}