ReuseStreams config and tests
This commit is contained in:
parent
841ec73497
commit
9fa9c45eda
|
|
@ -87,9 +87,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
_localEndPoint = localEndPoint;
|
||||
_prepareRequest = prepareRequest;
|
||||
_pathBase = context.ServerAddress.PathBase;
|
||||
_requestBody = new FrameRequestStream();
|
||||
_responseBody = new FrameResponseStream(this);
|
||||
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
|
||||
if (ReuseStreams)
|
||||
{
|
||||
_requestBody = new FrameRequestStream();
|
||||
_responseBody = new FrameResponseStream(this);
|
||||
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
|
||||
}
|
||||
|
||||
FrameControl = this;
|
||||
Reset();
|
||||
|
|
|
|||
|
|
@ -64,6 +64,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this);
|
||||
_keepAlive = messageBody.RequestKeepAlive;
|
||||
|
||||
// _duplexStream may be null if flag switched while running
|
||||
if (!ReuseStreams || _duplexStream == null)
|
||||
{
|
||||
_requestBody = new FrameRequestStream();
|
||||
_responseBody = new FrameResponseStream(this);
|
||||
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
|
||||
}
|
||||
|
||||
RequestBody = _requestBody.StartAcceptingReads(messageBody);
|
||||
ResponseBody = _responseBody.StartAcceptingWrites();
|
||||
DuplexStream = _duplexStream;
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
public class FrameRequestStream : Stream
|
||||
{
|
||||
private MessageBody _body;
|
||||
private StreamState _state;
|
||||
private FrameStreamState _state;
|
||||
|
||||
public FrameRequestStream()
|
||||
{
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
|
@ -115,9 +115,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
public Stream StartAcceptingReads(MessageBody body)
|
||||
{
|
||||
// Only start if not aborted
|
||||
if (_state == StreamState.Closed)
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Open;
|
||||
_state = FrameStreamState.Open;
|
||||
_body = body;
|
||||
}
|
||||
return this;
|
||||
|
|
@ -125,14 +125,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
|
||||
public void PauseAcceptingReads()
|
||||
{
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public void ResumeAcceptingReads()
|
||||
{
|
||||
if (_state == StreamState.Closed)
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Open;
|
||||
_state = FrameStreamState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
// Can't use dispose (or close) as can be disposed too early by user code
|
||||
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
_body = null;
|
||||
}
|
||||
|
||||
|
|
@ -148,9 +148,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
// We don't want to throw an ODE until the app func actually completes.
|
||||
// If the request is aborted, we throw an IOException instead.
|
||||
if (_state != StreamState.Closed)
|
||||
if (_state != FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Aborted;
|
||||
_state = FrameStreamState.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,20 +158,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
switch (_state)
|
||||
{
|
||||
case StreamState.Open:
|
||||
case FrameStreamState.Open:
|
||||
return;
|
||||
case StreamState.Closed:
|
||||
case FrameStreamState.Closed:
|
||||
throw new ObjectDisposedException(nameof(FrameRequestStream));
|
||||
case StreamState.Aborted:
|
||||
case FrameStreamState.Aborted:
|
||||
throw new IOException("The request has been aborted.");
|
||||
}
|
||||
}
|
||||
|
||||
private enum StreamState
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
Aborted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
class FrameResponseStream : Stream
|
||||
{
|
||||
private readonly FrameContext _context;
|
||||
private StreamState _state;
|
||||
private FrameStreamState _state;
|
||||
|
||||
public FrameResponseStream(FrameContext context)
|
||||
{
|
||||
_context = context;
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
|
@ -81,9 +81,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
public Stream StartAcceptingWrites()
|
||||
{
|
||||
// Only start if not aborted
|
||||
if (_state == StreamState.Closed)
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Open;
|
||||
_state = FrameStreamState.Open;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
@ -91,14 +91,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
|
||||
public void PauseAcceptingWrites()
|
||||
{
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public void ResumeAcceptingWrites()
|
||||
{
|
||||
if (_state == StreamState.Closed)
|
||||
if (_state == FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Open;
|
||||
_state = FrameStreamState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,16 +106,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
// Can't use dispose (or close) as can be disposed too early by user code
|
||||
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
||||
_state = StreamState.Closed;
|
||||
_state = FrameStreamState.Closed;
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
// We don't want to throw an ODE until the app func actually completes.
|
||||
// If the request is aborted, we throw an IOException instead.
|
||||
if (_state != StreamState.Closed)
|
||||
if (_state != FrameStreamState.Closed)
|
||||
{
|
||||
_state = StreamState.Aborted;
|
||||
_state = FrameStreamState.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,20 +123,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
switch (_state)
|
||||
{
|
||||
case StreamState.Open:
|
||||
case FrameStreamState.Open:
|
||||
return;
|
||||
case StreamState.Closed:
|
||||
case FrameStreamState.Closed:
|
||||
throw new ObjectDisposedException(nameof(FrameResponseStream));
|
||||
case StreamState.Aborted:
|
||||
case FrameStreamState.Aborted:
|
||||
throw new IOException("The request has been aborted.");
|
||||
}
|
||||
}
|
||||
|
||||
private enum StreamState
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
Aborted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +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.
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||
{
|
||||
enum FrameStreamState
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
Aborted
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,19 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
|
||||
bool NoDelay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that instructs <seealso cref="KestrelServer"/> whether it is safe to
|
||||
/// reuse the Request and Response <seealso cref="System.IO.Stream"/> objects
|
||||
/// for another request after the Response's OnCompleted callback has fired.
|
||||
/// When this is set to true it is not safe to retain references to these streams after this event has fired.
|
||||
/// It is false by default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When this is set to true it is not safe to retain references to these streams after this event has fired.
|
||||
/// It is false by default.
|
||||
/// </remarks>
|
||||
bool ReuseStreams { get; set; }
|
||||
|
||||
IConnectionFilter ConnectionFilter { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
ThreadPool = new LoggingThreadPool(trace),
|
||||
DateHeaderValueManager = dateHeaderValueManager,
|
||||
ConnectionFilter = information.ConnectionFilter,
|
||||
NoDelay = information.NoDelay
|
||||
NoDelay = information.NoDelay,
|
||||
ReuseStreams = information.ReuseStreams
|
||||
});
|
||||
|
||||
_disposables.Push(engine);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
Addresses = GetAddresses(configuration);
|
||||
ThreadCount = GetThreadCount(configuration);
|
||||
NoDelay = GetNoDelay(configuration);
|
||||
ReuseStreams = GetReuseStreams(configuration);
|
||||
}
|
||||
|
||||
public ICollection<string> Addresses { get; }
|
||||
|
|
@ -30,6 +31,8 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
|
||||
public bool NoDelay { get; set; }
|
||||
|
||||
public bool ReuseStreams { get; set; }
|
||||
|
||||
public IConnectionFilter ConnectionFilter { get; set; }
|
||||
|
||||
private static int ProcessorThreadCount
|
||||
|
|
@ -107,5 +110,18 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetReuseStreams(IConfiguration configuration)
|
||||
{
|
||||
var reuseStreamsString = configuration["kestrel.reuseStreams"];
|
||||
|
||||
bool reuseStreams;
|
||||
if (bool.TryParse(reuseStreamsString, out reuseStreams))
|
||||
{
|
||||
return reuseStreams;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
DateHeaderValueManager = context.DateHeaderValueManager;
|
||||
ConnectionFilter = context.ConnectionFilter;
|
||||
NoDelay = context.NoDelay;
|
||||
ReuseStreams = context.ReuseStreams;
|
||||
}
|
||||
|
||||
public IApplicationLifetime AppLifetime { get; set; }
|
||||
|
|
@ -41,5 +42,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
public IConnectionFilter ConnectionFilter { get; set; }
|
||||
|
||||
public bool NoDelay { get; set; }
|
||||
|
||||
public bool ReuseStreams { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
// 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.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
public class ReuseStreamsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReuseStreamsOn()
|
||||
{
|
||||
var streamCount = 0;
|
||||
var loopCount = 20;
|
||||
Stream lastStream = null;
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
{ "server.urls", "http://localhost:8801/" },
|
||||
{ "kestrel.reuseStreams", "true" }
|
||||
})
|
||||
.Build();
|
||||
|
||||
var hostBuilder = new WebHostBuilder(config);
|
||||
hostBuilder.UseServerFactory("Microsoft.AspNet.Server.Kestrel");
|
||||
hostBuilder.UseStartup(app =>
|
||||
{
|
||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
||||
app.Run(context =>
|
||||
{
|
||||
if (context.Request.Body != lastStream)
|
||||
{
|
||||
lastStream = context.Request.Body;
|
||||
streamCount++;
|
||||
}
|
||||
return context.Request.Body.CopyToAsync(context.Response.Body);
|
||||
});
|
||||
});
|
||||
|
||||
using (var app = hostBuilder.Build().Start())
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
for (int i = 0; i < loopCount; i++)
|
||||
{
|
||||
var content = $"{i} Hello World {i}";
|
||||
var request = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = new Uri("http://localhost:8801/"),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StringContent(content)
|
||||
};
|
||||
request.Headers.Add("Connection", new string[] { "Keep-Alive" });
|
||||
var responseMessage = await client.SendAsync(request);
|
||||
var result = await responseMessage.Content.ReadAsStringAsync();
|
||||
Assert.Equal(content, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(streamCount < loopCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReuseStreamsOff()
|
||||
{
|
||||
var streamCount = 0;
|
||||
var loopCount = 20;
|
||||
Stream lastStream = null;
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
{ "server.urls", "http://localhost:8802/" },
|
||||
{ "kestrel.reuseStreams", "false" }
|
||||
})
|
||||
.Build();
|
||||
|
||||
var hostBuilder = new WebHostBuilder(config);
|
||||
hostBuilder.UseServerFactory("Microsoft.AspNet.Server.Kestrel");
|
||||
hostBuilder.UseStartup(app =>
|
||||
{
|
||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
||||
app.Run(context =>
|
||||
{
|
||||
if (context.Request.Body != lastStream)
|
||||
{
|
||||
lastStream = context.Request.Body;
|
||||
streamCount++;
|
||||
}
|
||||
return context.Request.Body.CopyToAsync(context.Response.Body);
|
||||
});
|
||||
});
|
||||
|
||||
using (var app = hostBuilder.Build().Start())
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
for (int i = 0; i < loopCount; i++)
|
||||
{
|
||||
var content = $"{i} Hello World {i}";
|
||||
var request = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = new Uri("http://localhost:8802/"),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StringContent(content)
|
||||
};
|
||||
request.Headers.Add("Connection", new string[] { "Keep-Alive" });
|
||||
var responseMessage = await client.SendAsync(request);
|
||||
var result = await responseMessage.Content.ReadAsStringAsync();
|
||||
Assert.Equal(content, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(loopCount, streamCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,31 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
|||
Assert.False(information.NoDelay);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData("false", false)]
|
||||
[InlineData("False", false)]
|
||||
[InlineData("true", true)]
|
||||
[InlineData("True", true)]
|
||||
[InlineData("Foo", false)]
|
||||
[InlineData("FooBar", false)]
|
||||
public void SetReuseStreamsUsingConfiguration(string input, bool expected)
|
||||
{
|
||||
var values = new Dictionary<string, string>
|
||||
{
|
||||
{ "kestrel.reuseStreams", input }
|
||||
};
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(values)
|
||||
.Build();
|
||||
|
||||
var information = new KestrelServerInformation(configuration);
|
||||
|
||||
Assert.Equal(expected, information.ReuseStreams);
|
||||
}
|
||||
|
||||
private static int Clamp(int value, int min, int max)
|
||||
{
|
||||
return value < min ? min : value > max ? max : value;
|
||||
|
|
|
|||
Loading…
Reference in New Issue