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;
|
_localEndPoint = localEndPoint;
|
||||||
_prepareRequest = prepareRequest;
|
_prepareRequest = prepareRequest;
|
||||||
_pathBase = context.ServerAddress.PathBase;
|
_pathBase = context.ServerAddress.PathBase;
|
||||||
_requestBody = new FrameRequestStream();
|
if (ReuseStreams)
|
||||||
_responseBody = new FrameResponseStream(this);
|
{
|
||||||
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
|
_requestBody = new FrameRequestStream();
|
||||||
|
_responseBody = new FrameResponseStream(this);
|
||||||
|
_duplexStream = new FrameDuplexStream(_requestBody, _responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
FrameControl = this;
|
FrameControl = this;
|
||||||
Reset();
|
Reset();
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this);
|
var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this);
|
||||||
_keepAlive = messageBody.RequestKeepAlive;
|
_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);
|
RequestBody = _requestBody.StartAcceptingReads(messageBody);
|
||||||
ResponseBody = _responseBody.StartAcceptingWrites();
|
ResponseBody = _responseBody.StartAcceptingWrites();
|
||||||
DuplexStream = _duplexStream;
|
DuplexStream = _duplexStream;
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public class FrameRequestStream : Stream
|
public class FrameRequestStream : Stream
|
||||||
{
|
{
|
||||||
private MessageBody _body;
|
private MessageBody _body;
|
||||||
private StreamState _state;
|
private FrameStreamState _state;
|
||||||
|
|
||||||
public FrameRequestStream()
|
public FrameRequestStream()
|
||||||
{
|
{
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanRead { get { return true; } }
|
public override bool CanRead { get { return true; } }
|
||||||
|
|
@ -115,9 +115,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public Stream StartAcceptingReads(MessageBody body)
|
public Stream StartAcceptingReads(MessageBody body)
|
||||||
{
|
{
|
||||||
// Only start if not aborted
|
// Only start if not aborted
|
||||||
if (_state == StreamState.Closed)
|
if (_state == FrameStreamState.Closed)
|
||||||
{
|
{
|
||||||
_state = StreamState.Open;
|
_state = FrameStreamState.Open;
|
||||||
_body = body;
|
_body = body;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -125,14 +125,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
public void PauseAcceptingReads()
|
public void PauseAcceptingReads()
|
||||||
{
|
{
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResumeAcceptingReads()
|
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
|
// Can't use dispose (or close) as can be disposed too early by user code
|
||||||
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
_body = null;
|
_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.
|
// 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 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)
|
switch (_state)
|
||||||
{
|
{
|
||||||
case StreamState.Open:
|
case FrameStreamState.Open:
|
||||||
return;
|
return;
|
||||||
case StreamState.Closed:
|
case FrameStreamState.Closed:
|
||||||
throw new ObjectDisposedException(nameof(FrameRequestStream));
|
throw new ObjectDisposedException(nameof(FrameRequestStream));
|
||||||
case StreamState.Aborted:
|
case FrameStreamState.Aborted:
|
||||||
throw new IOException("The request has been 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
|
class FrameResponseStream : Stream
|
||||||
{
|
{
|
||||||
private readonly FrameContext _context;
|
private readonly FrameContext _context;
|
||||||
private StreamState _state;
|
private FrameStreamState _state;
|
||||||
|
|
||||||
public FrameResponseStream(FrameContext context)
|
public FrameResponseStream(FrameContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanRead => false;
|
public override bool CanRead => false;
|
||||||
|
|
@ -81,9 +81,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public Stream StartAcceptingWrites()
|
public Stream StartAcceptingWrites()
|
||||||
{
|
{
|
||||||
// Only start if not aborted
|
// Only start if not aborted
|
||||||
if (_state == StreamState.Closed)
|
if (_state == FrameStreamState.Closed)
|
||||||
{
|
{
|
||||||
_state = StreamState.Open;
|
_state = FrameStreamState.Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -91,14 +91,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
public void PauseAcceptingWrites()
|
public void PauseAcceptingWrites()
|
||||||
{
|
{
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResumeAcceptingWrites()
|
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
|
// Can't use dispose (or close) as can be disposed too early by user code
|
||||||
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
|
||||||
_state = StreamState.Closed;
|
_state = FrameStreamState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Abort()
|
public void Abort()
|
||||||
{
|
{
|
||||||
// We don't want to throw an ODE until the app func actually completes.
|
// 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 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)
|
switch (_state)
|
||||||
{
|
{
|
||||||
case StreamState.Open:
|
case FrameStreamState.Open:
|
||||||
return;
|
return;
|
||||||
case StreamState.Closed:
|
case FrameStreamState.Closed:
|
||||||
throw new ObjectDisposedException(nameof(FrameResponseStream));
|
throw new ObjectDisposedException(nameof(FrameResponseStream));
|
||||||
case StreamState.Aborted:
|
case FrameStreamState.Aborted:
|
||||||
throw new IOException("The request has been 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; }
|
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; }
|
IConnectionFilter ConnectionFilter { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
ThreadPool = new LoggingThreadPool(trace),
|
ThreadPool = new LoggingThreadPool(trace),
|
||||||
DateHeaderValueManager = dateHeaderValueManager,
|
DateHeaderValueManager = dateHeaderValueManager,
|
||||||
ConnectionFilter = information.ConnectionFilter,
|
ConnectionFilter = information.ConnectionFilter,
|
||||||
NoDelay = information.NoDelay
|
NoDelay = information.NoDelay,
|
||||||
|
ReuseStreams = information.ReuseStreams
|
||||||
});
|
});
|
||||||
|
|
||||||
_disposables.Push(engine);
|
_disposables.Push(engine);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
Addresses = GetAddresses(configuration);
|
Addresses = GetAddresses(configuration);
|
||||||
ThreadCount = GetThreadCount(configuration);
|
ThreadCount = GetThreadCount(configuration);
|
||||||
NoDelay = GetNoDelay(configuration);
|
NoDelay = GetNoDelay(configuration);
|
||||||
|
ReuseStreams = GetReuseStreams(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICollection<string> Addresses { get; }
|
public ICollection<string> Addresses { get; }
|
||||||
|
|
@ -30,6 +31,8 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
|
|
||||||
public bool NoDelay { get; set; }
|
public bool NoDelay { get; set; }
|
||||||
|
|
||||||
|
public bool ReuseStreams { get; set; }
|
||||||
|
|
||||||
public IConnectionFilter ConnectionFilter { get; set; }
|
public IConnectionFilter ConnectionFilter { get; set; }
|
||||||
|
|
||||||
private static int ProcessorThreadCount
|
private static int ProcessorThreadCount
|
||||||
|
|
@ -107,5 +110,18 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
|
|
||||||
return true;
|
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;
|
DateHeaderValueManager = context.DateHeaderValueManager;
|
||||||
ConnectionFilter = context.ConnectionFilter;
|
ConnectionFilter = context.ConnectionFilter;
|
||||||
NoDelay = context.NoDelay;
|
NoDelay = context.NoDelay;
|
||||||
|
ReuseStreams = context.ReuseStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IApplicationLifetime AppLifetime { get; set; }
|
public IApplicationLifetime AppLifetime { get; set; }
|
||||||
|
|
@ -41,5 +42,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
||||||
public IConnectionFilter ConnectionFilter { get; set; }
|
public IConnectionFilter ConnectionFilter { get; set; }
|
||||||
|
|
||||||
public bool NoDelay { 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);
|
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)
|
private static int Clamp(int value, int min, int max)
|
||||||
{
|
{
|
||||||
return value < min ? min : value > max ? max : value;
|
return value < min ? min : value > max ? max : value;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue