Create a direct way to configure Kestrel endpoints
- Replace endpoint configuration via .UseUrls() or --server.urls with Listen* methods on KestrelSerrverOptions. - Replace IConnectionFilter with IConnectionAdapter which no longer exposes ServerAddress via a context. - Simplify libuv Listener classes - Support systemd socket activation - Add docker-based test for systemd socket activation to be run on Travis
This commit is contained in:
parent
b46e48fe01
commit
2351c1b558
|
|
@ -1,6 +1,8 @@
|
|||
language: csharp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
|
|
@ -30,3 +32,4 @@ before_install:
|
|||
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi
|
||||
script:
|
||||
- ./build.sh --quiet verify
|
||||
- if test "$TRAVIS_OS_NAME" != "osx"; then bash test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh; fi
|
||||
|
|
|
|||
|
|
@ -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.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -40,8 +41,10 @@ namespace LargeResponseApp
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://localhost:5001/")
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(IPAddress.Loopback, 5001);
|
||||
})
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -33,24 +34,33 @@ namespace SampleApp
|
|||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
var hostBuilder = new WebHostBuilder().UseKestrel(options =>
|
||||
{
|
||||
options.Listen(IPAddress.Loopback, 5000, listenOptions =>
|
||||
{
|
||||
// options.ThreadCount = 4;
|
||||
options.NoDelay = true;
|
||||
options.UseHttps("testCert.pfx", "testPassword");
|
||||
options.UseConnectionLogging();
|
||||
})
|
||||
.UseUrls("http://localhost:5000", "https://localhost:5001")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
// Uncomment the following to enable Nagle's algorithm for this endpoint.
|
||||
//listenOptions.NoDelay = false;
|
||||
|
||||
// The following section should be used to demo sockets
|
||||
//var addresses = application.GetAddresses();
|
||||
//addresses.Clear();
|
||||
//addresses.Add("http://unix:/tmp/kestrel-test.sock");
|
||||
listenOptions.UseConnectionLogging();
|
||||
});
|
||||
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("testCert.pfx", "testPassword");
|
||||
listenOptions.UseConnectionLogging();
|
||||
});
|
||||
|
||||
options.UseSystemd();
|
||||
|
||||
// The following section should be used to demo sockets
|
||||
//options.ListenUnixSocket("/tmp/kestrel-test.sock");
|
||||
|
||||
// Uncomment the following line to change the default number of libuv threads for all endpoints.
|
||||
//options.ThreadCount = 4;
|
||||
})
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>();
|
||||
|
||||
var host = hostBuilder.Build();
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
// 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.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||
{
|
||||
public class HttpsConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
private static readonly ClosedAdaptedConnection _closedAdaptedConnection = new ClosedAdaptedConnection();
|
||||
|
||||
private readonly HttpsConnectionAdapterOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options)
|
||||
: this(options, loggerFactory: null)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
if (options.ServerCertificate == null)
|
||||
{
|
||||
throw new ArgumentException("The server certificate parameter is required.");
|
||||
}
|
||||
|
||||
_options = options;
|
||||
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter));
|
||||
}
|
||||
|
||||
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
SslStream sslStream;
|
||||
bool certificateRequired;
|
||||
|
||||
if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
|
||||
{
|
||||
sslStream = new SslStream(context.ConnectionStream);
|
||||
certificateRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sslStream = new SslStream(context.ConnectionStream, leaveInnerStreamOpen: false,
|
||||
userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) =>
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
return _options.ClientCertificateMode != ClientCertificateMode.RequireCertificate;
|
||||
}
|
||||
|
||||
if (_options.ClientCertificateValidation == null)
|
||||
{
|
||||
if (sslPolicyErrors != SslPolicyErrors.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var certificate2 = ConvertToX509Certificate2(certificate);
|
||||
if (certificate2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_options.ClientCertificateValidation != null)
|
||||
{
|
||||
if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
certificateRequired = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await sslStream.AuthenticateAsServerAsync(_options.ServerCertificate, certificateRequired,
|
||||
_options.SslProtocols, _options.CheckCertificateRevocation);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger?.LogInformation(1, ex, "Failed to authenticate HTTPS connection.");
|
||||
sslStream.Dispose();
|
||||
return _closedAdaptedConnection;
|
||||
}
|
||||
|
||||
return new HttpsAdaptedConnection(sslStream);
|
||||
}
|
||||
|
||||
private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate)
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
X509Certificate2 certificate2 = certificate as X509Certificate2;
|
||||
if (certificate2 != null)
|
||||
{
|
||||
return certificate2;
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
// conversion X509Certificate to X509Certificate2 not supported
|
||||
// https://github.com/dotnet/corefx/issues/4510
|
||||
return null;
|
||||
#else
|
||||
return new X509Certificate2(certificate);
|
||||
#endif
|
||||
}
|
||||
|
||||
private class HttpsAdaptedConnection : IAdaptedConnection
|
||||
{
|
||||
private readonly SslStream _sslStream;
|
||||
|
||||
public HttpsAdaptedConnection(SslStream sslStream)
|
||||
{
|
||||
_sslStream = sslStream;
|
||||
}
|
||||
|
||||
public Stream ConnectionStream => _sslStream;
|
||||
|
||||
public void PrepareRequest(IFeatureCollection requestFeatures)
|
||||
{
|
||||
var clientCertificate = ConvertToX509Certificate2(_sslStream.RemoteCertificate);
|
||||
if (clientCertificate != null)
|
||||
{
|
||||
requestFeatures.Set<ITlsConnectionFeature>(new TlsConnectionFeature { ClientCertificate = clientCertificate });
|
||||
}
|
||||
|
||||
requestFeatures.Get<IHttpRequestFeature>().Scheme = "https";
|
||||
}
|
||||
}
|
||||
|
||||
private class ClosedAdaptedConnection : IAdaptedConnection
|
||||
{
|
||||
public Stream ConnectionStream { get; } = new ClosedStream();
|
||||
|
||||
public void PrepareRequest(IFeatureCollection requestFeatures)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,9 @@ using System.Security.Cryptography.X509Certificates;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||
{
|
||||
public class HttpsConnectionFilterOptions
|
||||
public class HttpsConnectionAdapterOptions
|
||||
{
|
||||
public HttpsConnectionFilterOptions()
|
||||
public HttpsConnectionAdapterOptions()
|
||||
{
|
||||
ClientCertificateMode = ClientCertificateMode.NoCertificate;
|
||||
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
// 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.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||
{
|
||||
public class HttpsConnectionFilter : IConnectionFilter
|
||||
{
|
||||
private static readonly ClosedStream _closedStream = new ClosedStream();
|
||||
|
||||
private readonly HttpsConnectionFilterOptions _options;
|
||||
private readonly IConnectionFilter _previous;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HttpsConnectionFilter(HttpsConnectionFilterOptions options, IConnectionFilter previous)
|
||||
: this(options, previous, loggerFactory: null)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpsConnectionFilter(HttpsConnectionFilterOptions options, IConnectionFilter previous, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
if (previous == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(previous));
|
||||
}
|
||||
if (options.ServerCertificate == null)
|
||||
{
|
||||
throw new ArgumentException("The server certificate parameter is required.");
|
||||
}
|
||||
|
||||
_options = options;
|
||||
_previous = previous;
|
||||
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionFilter));
|
||||
}
|
||||
|
||||
public async Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
{
|
||||
await _previous.OnConnectionAsync(context);
|
||||
|
||||
if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SslStream sslStream;
|
||||
bool certificateRequired;
|
||||
|
||||
if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
|
||||
{
|
||||
sslStream = new SslStream(context.Connection);
|
||||
certificateRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sslStream = new SslStream(context.Connection, leaveInnerStreamOpen: false,
|
||||
userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) =>
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
return _options.ClientCertificateMode != ClientCertificateMode.RequireCertificate;
|
||||
}
|
||||
|
||||
if (_options.ClientCertificateValidation == null)
|
||||
{
|
||||
if (sslPolicyErrors != SslPolicyErrors.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var certificate2 = ConvertToX509Certificate2(certificate);
|
||||
if (certificate2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_options.ClientCertificateValidation != null)
|
||||
{
|
||||
if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
certificateRequired = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await sslStream.AuthenticateAsServerAsync(_options.ServerCertificate, certificateRequired,
|
||||
_options.SslProtocols, _options.CheckCertificateRevocation);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger?.LogInformation(1, ex, "Failed to authenticate HTTPS connection.");
|
||||
|
||||
sslStream.Dispose();
|
||||
context.Connection = _closedStream;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var previousPrepareRequest = context.PrepareRequest;
|
||||
context.PrepareRequest = features =>
|
||||
{
|
||||
previousPrepareRequest?.Invoke(features);
|
||||
|
||||
var clientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate);
|
||||
if (clientCertificate != null)
|
||||
{
|
||||
features.Set<ITlsConnectionFeature>(new TlsConnectionFeature { ClientCertificate = clientCertificate });
|
||||
}
|
||||
|
||||
features.Get<IHttpRequestFeature>().Scheme = "https";
|
||||
};
|
||||
|
||||
context.Connection = sslStream;
|
||||
}
|
||||
}
|
||||
|
||||
private X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate)
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
X509Certificate2 certificate2 = certificate as X509Certificate2;
|
||||
if (certificate2 != null)
|
||||
{
|
||||
return certificate2;
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
// conversion X509Certificate to X509Certificate2 not supported
|
||||
// https://github.com/dotnet/corefx/issues/4510
|
||||
return null;
|
||||
#else
|
||||
return new X509Certificate2(certificate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
// 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.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class KestrelServerOptionsHttpsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="options">
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||
/// </param>
|
||||
/// <param name="fileName">
|
||||
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, string fileName)
|
||||
{
|
||||
var env = options.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
return options.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="options">
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||
/// </param>
|
||||
/// <param name="fileName">
|
||||
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||
/// </param>
|
||||
/// <param name="password">
|
||||
/// The password required to access the X.509 certificate data.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, string fileName, string password)
|
||||
{
|
||||
var env = options.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
return options.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName), password));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="options">
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||
/// </param>
|
||||
/// <param name="serverCertificate">
|
||||
/// The X.509 certificate.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, X509Certificate2 serverCertificate)
|
||||
{
|
||||
return options.UseHttps(new HttpsConnectionFilterOptions { ServerCertificate = serverCertificate });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="options">
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions to configure.
|
||||
/// </param>
|
||||
/// <param name="httpsOptions">
|
||||
/// Options to configure HTTPS.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseHttps(this KestrelServerOptions options, HttpsConnectionFilterOptions httpsOptions)
|
||||
{
|
||||
var prevFilter = options.ConnectionFilter ?? new NoOpConnectionFilter();
|
||||
var loggerFactory = options.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
options.ConnectionFilter = new HttpsConnectionFilter(httpsOptions, prevFilter, loggerFactory);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// 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.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods fro <see cref="ListenOptions"/> that configure Kestrel to use HTTPS for a given endpoint.
|
||||
/// </summary>
|
||||
public static class ListenOptionsHttpsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="listenOptions">
|
||||
/// The <see cref="ListenOptions"/> to configure.
|
||||
/// </param>
|
||||
/// <param name="fileName">
|
||||
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName)
|
||||
{
|
||||
var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
return listenOptions.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="listenOptions">
|
||||
/// The <see cref="ListenOptions"/> to configure.
|
||||
/// </param>
|
||||
/// <param name="fileName">
|
||||
/// The name of a certificate file, relative to the directory that contains the application content files.
|
||||
/// </param>
|
||||
/// <param name="password">
|
||||
/// The password required to access the X.509 certificate data.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName, string password)
|
||||
{
|
||||
var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
return listenOptions.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName), password));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="listenOptions">
|
||||
/// The <see cref="ListenOptions"/> to configure.
|
||||
/// </param>
|
||||
/// <param name="serverCertificate">
|
||||
/// The X.509 certificate.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseHttps(this ListenOptions listenOptions, X509Certificate2 serverCertificate)
|
||||
{
|
||||
return listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = serverCertificate });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure Kestrel to use HTTPS.
|
||||
/// </summary>
|
||||
/// <param name="listenOptions">
|
||||
/// The <see cref="ListenOptions"/> to configure.
|
||||
/// </param>
|
||||
/// <param name="httpsOptions">
|
||||
/// Options to configure HTTPS.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions)
|
||||
{
|
||||
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
listenOptions.ConnectionAdapters.Add(new HttpsConnectionAdapter(httpsOptions, loggerFactory));
|
||||
return listenOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsHttpsExtensions",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionFilterOptions",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
|
||||
{
|
||||
// Even though this only includes the non-adapted ConnectionStream currently, this is a context in case
|
||||
// we want to add more connection metadata later.
|
||||
public class ConnectionAdapterContext
|
||||
{
|
||||
internal ConnectionAdapterContext(Stream connectionStream)
|
||||
{
|
||||
ConnectionStream = connectionStream;
|
||||
}
|
||||
|
||||
public Stream ConnectionStream { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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 Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
|
||||
{
|
||||
public interface IAdaptedConnection
|
||||
{
|
||||
Stream ConnectionStream { get; }
|
||||
|
||||
void PrepareRequest(IFeatureCollection requestFeatures);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
|
||||
{
|
||||
public interface IConnectionAdapter
|
||||
{
|
||||
Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,13 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
||||
{
|
||||
public class FilteredStreamAdapter : IDisposable
|
||||
public class AdaptedPipeline : IDisposable
|
||||
{
|
||||
private readonly Stream _filteredStream;
|
||||
|
||||
public FilteredStreamAdapter(
|
||||
public AdaptedPipeline(
|
||||
string connectionId,
|
||||
Stream filteredStream,
|
||||
MemoryPool memory,
|
||||
|
|
@ -8,7 +8,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
||||
{
|
||||
internal class LoggingStream : Stream
|
||||
{
|
||||
|
|
@ -8,16 +8,16 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
||||
{
|
||||
public class LibuvStream : Stream
|
||||
public class RawStream : Stream
|
||||
{
|
||||
private readonly SocketInput _input;
|
||||
private readonly ISocketOutput _output;
|
||||
|
||||
private Task<int> _cachedTask = TaskCache<int>.DefaultCompletedTask;
|
||||
|
||||
public LibuvStream(SocketInput input, ISocketOutput output)
|
||||
public RawStream(SocketInput input, ISocketOutput output)
|
||||
{
|
||||
_input = input;
|
||||
_output = output;
|
||||
|
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter.Internal
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal
|
||||
{
|
||||
public class StreamSocketOutput : ISocketOutput
|
||||
{
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class ListenOptionsConnectionLoggingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Emits verbose logs for bytes read from and written to the connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions)
|
||||
{
|
||||
return listenOptions.UseConnectionLogging(nameof(LoggingConnectionAdapter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits verbose logs for bytes read from and written to the connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="ListenOptions"/>.
|
||||
/// </returns>
|
||||
public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName)
|
||||
{
|
||||
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionAdapter));
|
||||
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger));
|
||||
return listenOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// 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.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Adapter
|
||||
{
|
||||
public class LoggingConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public LoggingConnectionAdapter(ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
return Task.FromResult<IAdaptedConnection>(
|
||||
new LoggingAdaptedConnection(context.ConnectionStream, _logger));
|
||||
}
|
||||
|
||||
private class LoggingAdaptedConnection : IAdaptedConnection
|
||||
{
|
||||
public LoggingAdaptedConnection(Stream rawStream, ILogger logger)
|
||||
{
|
||||
ConnectionStream = new LoggingStream(rawStream, logger);
|
||||
}
|
||||
|
||||
public Stream ConnectionStream { get; }
|
||||
|
||||
public void PrepareRequest(IFeatureCollection requestFeatures)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
||||
{
|
||||
public class ConnectionFilterContext
|
||||
{
|
||||
public ServerAddress Address { get; set; }
|
||||
public Stream Connection { get; set; }
|
||||
public Action<IFeatureCollection> PrepareRequest { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
||||
{
|
||||
public interface IConnectionFilter
|
||||
{
|
||||
Task OnConnectionAsync(ConnectionFilterContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class KestrelServerOptionsConnectionLoggingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Emits verbose logs for bytes read from and written to the connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseConnectionLogging(this KestrelServerOptions options)
|
||||
{
|
||||
return options.UseConnectionLogging(nameof(LoggingConnectionFilter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits verbose logs for bytes read from and written to the connection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Server.KestrelServerOptions.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseConnectionLogging(this KestrelServerOptions options, string loggerName)
|
||||
{
|
||||
var prevFilter = options.ConnectionFilter ?? new NoOpConnectionFilter();
|
||||
var loggerFactory = options.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionFilter));
|
||||
options.ConnectionFilter = new LoggingConnectionFilter(logger, prevFilter);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
||||
{
|
||||
public class LoggingConnectionFilter : IConnectionFilter
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IConnectionFilter _previous;
|
||||
|
||||
public LoggingConnectionFilter(ILogger logger, IConnectionFilter previous)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
if (previous == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(previous));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
_previous = previous;
|
||||
}
|
||||
|
||||
public async Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
{
|
||||
await _previous.OnConnectionAsync(context);
|
||||
|
||||
context.Connection = new LoggingStream(context.Connection, _logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// 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.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Filter
|
||||
{
|
||||
public class NoOpConnectionFilter : IConnectionFilter
|
||||
{
|
||||
public Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,13 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
|
@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private static readonly Action<UvStreamHandle, int, object> _readCallback =
|
||||
(handle, status, state) => ReadCallback(handle, status, state);
|
||||
|
||||
private static readonly Func<UvStreamHandle, int, object, Libuv.uv_buf_t> _allocCallback =
|
||||
(handle, suggestedsize, state) => AllocCallback(handle, suggestedsize, state);
|
||||
|
||||
|
|
@ -32,9 +34,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private readonly UvStreamHandle _socket;
|
||||
private readonly Frame _frame;
|
||||
private ConnectionFilterContext _filterContext;
|
||||
private LibuvStream _libuvStream;
|
||||
private FilteredStreamAdapter _filteredStreamAdapter;
|
||||
private readonly List<IConnectionAdapter> _connectionAdapters;
|
||||
private AdaptedPipeline _adaptedPipeline;
|
||||
private Stream _filteredStream;
|
||||
private Task _readInputTask;
|
||||
|
||||
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
||||
|
|
@ -47,6 +49,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
|
||||
{
|
||||
_socket = socket;
|
||||
_connectionAdapters = context.ListenOptions.ConnectionAdapters;
|
||||
socket.Connection = this;
|
||||
ConnectionControl = this;
|
||||
|
||||
|
|
@ -80,57 +83,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private Func<ConnectionContext, Frame> FrameFactory => ListenerContext.ServiceContext.FrameFactory;
|
||||
private IKestrelTrace Log => ListenerContext.ServiceContext.Log;
|
||||
private IThreadPool ThreadPool => ListenerContext.ServiceContext.ThreadPool;
|
||||
private ServerAddress ServerAddress => ListenerContext.ServerAddress;
|
||||
private KestrelThread Thread => ListenerContext.Thread;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Log.ConnectionStart(ConnectionId);
|
||||
|
||||
// Start socket prior to applying the ConnectionFilter
|
||||
// Start socket prior to applying the ConnectionAdapter
|
||||
_socket.ReadStart(_allocCallback, _readCallback, this);
|
||||
|
||||
if (ServerOptions.ConnectionFilter == null)
|
||||
if (_connectionAdapters.Count == 0)
|
||||
{
|
||||
_frame.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_libuvStream = new LibuvStream(Input, Output);
|
||||
|
||||
_filterContext = new ConnectionFilterContext
|
||||
{
|
||||
Connection = _libuvStream,
|
||||
Address = ServerAddress
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ServerOptions.ConnectionFilter.OnConnectionAsync(_filterContext).ContinueWith((task, state) =>
|
||||
{
|
||||
var connection = (Connection)state;
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
connection.Log.LogError(0, task.Exception, "ConnectionFilter.OnConnection");
|
||||
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||
}
|
||||
else if (task.IsCanceled)
|
||||
{
|
||||
connection.Log.LogError("ConnectionFilter.OnConnection Canceled");
|
||||
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.ApplyConnectionFilter();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogError(0, ex, "ConnectionFilter.OnConnection");
|
||||
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||
}
|
||||
// ApplyConnectionAdaptersAsync should never throw. If it succeeds, it will call _frame.Start().
|
||||
// Otherwise, it will close the connection.
|
||||
var ignore = ApplyConnectionAdaptersAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,13 +131,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
var connection = (Connection)state;
|
||||
|
||||
if (_filteredStreamAdapter != null)
|
||||
if (connection._adaptedPipeline != null)
|
||||
{
|
||||
Task.WhenAll(_readInputTask, _frame.StopAsync()).ContinueWith((task2, state2) =>
|
||||
Task.WhenAll(connection._readInputTask, connection._frame.StopAsync()).ContinueWith((task2, state2) =>
|
||||
{
|
||||
var connection2 = (Connection)state2;
|
||||
connection2._filterContext.Connection.Dispose();
|
||||
connection2._filteredStreamAdapter.Dispose();
|
||||
connection2._filteredStream.Dispose();
|
||||
connection2._adaptedPipeline.Dispose();
|
||||
}, connection);
|
||||
}
|
||||
}, this);
|
||||
|
|
@ -187,31 +157,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
|
||||
}
|
||||
|
||||
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
||||
}
|
||||
|
||||
private void ApplyConnectionFilter()
|
||||
private async Task ApplyConnectionAdaptersAsync()
|
||||
{
|
||||
if (_filterContext.Connection != _libuvStream)
|
||||
try
|
||||
{
|
||||
_filteredStreamAdapter = new FilteredStreamAdapter(ConnectionId, _filterContext.Connection, Thread.Memory, Log, ThreadPool, _bufferSizeControl);
|
||||
var rawStream = new RawStream(Input, Output);
|
||||
var adapterContext = new ConnectionAdapterContext(rawStream);
|
||||
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
|
||||
|
||||
_frame.Input = _filteredStreamAdapter.SocketInput;
|
||||
_frame.Output = _filteredStreamAdapter.SocketOutput;
|
||||
for (var i = 0; i < _connectionAdapters.Count; i++)
|
||||
{
|
||||
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
|
||||
adaptedConnections[i] = adaptedConnection;
|
||||
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
|
||||
}
|
||||
|
||||
// Don't attempt to read input if connection has already closed.
|
||||
// This can happen if a client opens a connection and immediately closes it.
|
||||
_readInputTask = _socketClosedTcs.Task.Status == TaskStatus.WaitingForActivation ?
|
||||
_filteredStreamAdapter.ReadInputAsync() :
|
||||
TaskCache.CompletedTask;
|
||||
if (adapterContext.ConnectionStream != rawStream)
|
||||
{
|
||||
_filteredStream = adapterContext.ConnectionStream;
|
||||
_adaptedPipeline = new AdaptedPipeline(ConnectionId, adapterContext.ConnectionStream,
|
||||
Thread.Memory, Log, ThreadPool, _bufferSizeControl);
|
||||
|
||||
_frame.Input = _adaptedPipeline.SocketInput;
|
||||
_frame.Output = _adaptedPipeline.SocketOutput;
|
||||
|
||||
// Don't attempt to read input if connection has already closed.
|
||||
// This can happen if a client opens a connection and immediately closes it.
|
||||
_readInputTask = _socketClosedTcs.Task.Status == TaskStatus.WaitingForActivation
|
||||
? _adaptedPipeline.ReadInputAsync()
|
||||
: TaskCache.CompletedTask;
|
||||
}
|
||||
|
||||
_frame.AdaptedConnections = adaptedConnections;
|
||||
_frame.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
|
||||
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||
}
|
||||
|
||||
_frame.PrepareRequest = _filterContext.PrepareRequest;
|
||||
_frame.Start();
|
||||
}
|
||||
|
||||
private static Libuv.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public IPEndPoint LocalEndPoint { get; set; }
|
||||
|
||||
public string ConnectionId { get; set; }
|
||||
|
||||
public Action<IFeatureCollection> PrepareRequest { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
ServerOptions = context.ListenerContext.ServiceContext.ServerOptions;
|
||||
|
||||
_pathBase = ServerAddress.PathBase;
|
||||
_pathBase = context.ListenerContext.ListenOptions.PathBase;
|
||||
|
||||
FrameControl = this;
|
||||
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||
|
|
@ -97,23 +98,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public ConnectionContext ConnectionContext { get; }
|
||||
public SocketInput Input { get; set; }
|
||||
public ISocketOutput Output { get; set; }
|
||||
public Action<IFeatureCollection> PrepareRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
return ConnectionContext.PrepareRequest;
|
||||
}
|
||||
set
|
||||
{
|
||||
ConnectionContext.PrepareRequest = value;
|
||||
}
|
||||
}
|
||||
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
|
||||
|
||||
protected IConnectionControl ConnectionControl => ConnectionContext.ConnectionControl;
|
||||
protected IKestrelTrace Log => ConnectionContext.ListenerContext.ServiceContext.Log;
|
||||
|
||||
private DateHeaderValueManager DateHeaderValueManager => ConnectionContext.ListenerContext.ServiceContext.DateHeaderValueManager;
|
||||
private ServerAddress ServerAddress => ConnectionContext.ListenerContext.ServerAddress;
|
||||
// Hold direct reference to ServerOptions since this is used very often in the request processing path
|
||||
private KestrelServerOptions ServerOptions { get; }
|
||||
private IPEndPoint LocalEndPoint => ConnectionContext.LocalEndPoint;
|
||||
|
|
@ -367,7 +357,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
LocalPort = LocalEndPoint?.Port ?? 0;
|
||||
ConnectionIdFeature = ConnectionId;
|
||||
|
||||
PrepareRequest?.Invoke(this);
|
||||
if (AdaptedConnections != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var adaptedConnection in AdaptedConnections)
|
||||
{
|
||||
adaptedConnection.PrepareRequest(this);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IAdaptedConnection.PrepareRequest)} method of an {nameof(IAdaptedConnection)}.");
|
||||
}
|
||||
}
|
||||
|
||||
_manuallySetRequestAbortToken = null;
|
||||
_abortedCts = null;
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// <summary>
|
||||
/// Base class for listeners in Kestrel. Listens for incoming connections
|
||||
/// </summary>
|
||||
public abstract class Listener : ListenerContext, IAsyncDisposable
|
||||
public class Listener : ListenerContext, IAsyncDisposable
|
||||
{
|
||||
private bool _closed;
|
||||
|
||||
protected Listener(ServiceContext serviceContext)
|
||||
public Listener(ServiceContext serviceContext)
|
||||
: base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
|
@ -26,20 +26,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public IKestrelTrace Log => ServiceContext.Log;
|
||||
|
||||
public Task StartAsync(
|
||||
ServerAddress address,
|
||||
ListenOptions listenOptions,
|
||||
KestrelThread thread)
|
||||
{
|
||||
ServerAddress = address;
|
||||
ListenOptions = listenOptions;
|
||||
Thread = thread;
|
||||
|
||||
var tcs = new TaskCompletionSource<int>(this);
|
||||
|
||||
Thread.Post(state =>
|
||||
{
|
||||
var tcs2 = (TaskCompletionSource<int>)state;
|
||||
var tcs2 = (TaskCompletionSource<int>) state;
|
||||
try
|
||||
{
|
||||
var listener = ((Listener)tcs2.Task.AsyncState);
|
||||
var listener = ((Listener) tcs2.Task.AsyncState);
|
||||
listener.ListenSocket = listener.CreateListenSocket();
|
||||
ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
|
||||
tcs2.SetResult(0);
|
||||
|
|
@ -56,11 +56,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// <summary>
|
||||
/// Creates the socket used to listen for incoming connections
|
||||
/// </summary>
|
||||
protected abstract UvStreamHandle CreateListenSocket();
|
||||
private UvStreamHandle CreateListenSocket()
|
||||
{
|
||||
switch (ListenOptions.Type)
|
||||
{
|
||||
case ListenType.IPEndPoint:
|
||||
case ListenType.FileHandle:
|
||||
var socket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
socket.NoDelay(ListenOptions.NoDelay);
|
||||
|
||||
if (ListenOptions.Type == ListenType.IPEndPoint)
|
||||
{
|
||||
socket.Bind(ListenOptions.IPEndPoint);
|
||||
|
||||
// If requested port was "0", replace with assigned dynamic port.
|
||||
ListenOptions.IPEndPoint = socket.GetSockIPEndPoint();
|
||||
}
|
||||
else
|
||||
{
|
||||
socket.Open((IntPtr)ListenOptions.FileHandle);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return socket;
|
||||
case ListenType.SocketPath:
|
||||
var pipe = new UvPipeHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
pipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
pipe.Bind(ListenOptions.SocketPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
pipe.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return pipe;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state)
|
||||
{
|
||||
var listener = (Listener)state;
|
||||
var listener = (Listener) state;
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
|
|
@ -77,7 +127,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// </summary>
|
||||
/// <param name="listenSocket">Socket being used to listen on</param>
|
||||
/// <param name="status">Connection status</param>
|
||||
protected abstract void OnConnection(UvStreamHandle listenSocket, int status);
|
||||
private void OnConnection(UvStreamHandle listenSocket, int status)
|
||||
{
|
||||
UvStreamHandle acceptSocket = null;
|
||||
|
||||
try
|
||||
{
|
||||
acceptSocket = CreateAcceptSocket();
|
||||
listenSocket.Accept(acceptSocket);
|
||||
DispatchConnection(acceptSocket);
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
Log.LogError(0, ex, "Listener.OnConnection");
|
||||
acceptSocket?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DispatchConnection(UvStreamHandle socket)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
public class ListenerContext
|
||||
|
|
@ -12,10 +15,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
public ServiceContext ServiceContext { get; set; }
|
||||
|
||||
public ServerAddress ServerAddress { get; set; }
|
||||
public ListenOptions ListenOptions { get; set; }
|
||||
|
||||
public KestrelThread Thread { get; set; }
|
||||
|
||||
public KestrelServerOptions ServerOptions => ServiceContext.ServerOptions;
|
||||
/// <summary>
|
||||
/// Creates a socket which can be used to accept an incoming connection.
|
||||
/// </summary>
|
||||
protected UvStreamHandle CreateAcceptSocket()
|
||||
{
|
||||
switch (ListenOptions.Type)
|
||||
{
|
||||
case ListenType.IPEndPoint:
|
||||
case ListenType.FileHandle:
|
||||
var tcpHandle = new UvTcpHandle(ServiceContext.Log);
|
||||
tcpHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
tcpHandle.NoDelay(ListenOptions.NoDelay);
|
||||
return tcpHandle;
|
||||
case ListenType.SocketPath:
|
||||
var pipeHandle = new UvPipeHandle(ServiceContext.Log);
|
||||
pipeHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
return pipeHandle;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// A primary listener waits for incoming connections on a specified socket. Incoming
|
||||
/// connections may be passed to a secondary listener to handle.
|
||||
/// </summary>
|
||||
public abstract class ListenerPrimary : Listener
|
||||
public class ListenerPrimary : Listener
|
||||
{
|
||||
private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>();
|
||||
private int _dispatchIndex;
|
||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// but it has no other functional significance
|
||||
private readonly ArraySegment<ArraySegment<byte>> _dummyMessage = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) });
|
||||
|
||||
protected ListenerPrimary(ServiceContext serviceContext) : base(serviceContext)
|
||||
public ListenerPrimary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public async Task StartAsync(
|
||||
string pipeName,
|
||||
byte[] pipeMessage,
|
||||
ServerAddress address,
|
||||
ListenOptions listenOptions,
|
||||
KestrelThread thread)
|
||||
{
|
||||
_pipeName = pipeName;
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
Marshal.StructureToPtr(fileCompletionInfo, _fileCompletionInfoPtr, false);
|
||||
}
|
||||
|
||||
await StartAsync(address, thread).ConfigureAwait(false);
|
||||
await StartAsync(listenOptions, thread).ConfigureAwait(false);
|
||||
|
||||
await Thread.PostAsync(state => ((ListenerPrimary)state).PostCallback(),
|
||||
this).ConfigureAwait(false);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// A secondary listener is delegated requests from a primary listener via a named pipe or
|
||||
/// UNIX domain socket.
|
||||
/// </summary>
|
||||
public abstract class ListenerSecondary : ListenerContext, IAsyncDisposable
|
||||
public class ListenerSecondary : ListenerContext, IAsyncDisposable
|
||||
{
|
||||
private string _pipeName;
|
||||
private byte[] _pipeMessage;
|
||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private Libuv.uv_buf_t _buf;
|
||||
private bool _closed;
|
||||
|
||||
protected ListenerSecondary(ServiceContext serviceContext) : base(serviceContext)
|
||||
public ListenerSecondary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
_ptr = Marshal.AllocHGlobal(4);
|
||||
}
|
||||
|
|
@ -35,14 +35,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
public Task StartAsync(
|
||||
string pipeName,
|
||||
byte[] pipeMessage,
|
||||
ServerAddress address,
|
||||
ListenOptions listenOptions,
|
||||
KestrelThread thread)
|
||||
{
|
||||
_pipeName = pipeName;
|
||||
_pipeMessage = pipeMessage;
|
||||
_buf = thread.Loop.Libuv.buf_init(_ptr, 4);
|
||||
|
||||
ServerAddress = address;
|
||||
ListenOptions = listenOptions;
|
||||
Thread = thread;
|
||||
DispatchPipe = new UvPipeHandle(Log);
|
||||
|
||||
|
|
@ -173,11 +173,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a socket which can be used to accept an incoming connection
|
||||
/// </summary>
|
||||
protected abstract UvStreamHandle CreateAcceptSocket();
|
||||
|
||||
private void FreeBuffer()
|
||||
{
|
||||
var ptr = Interlocked.Exchange(ref _ptr, IntPtr.Zero);
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="Listener"/> that uses UNIX domain sockets as its transport.
|
||||
/// </summary>
|
||||
public class PipeListener : Listener
|
||||
{
|
||||
public PipeListener(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the socket used to listen for incoming connections
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateListenSocket()
|
||||
{
|
||||
var socket = new UvPipeHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
socket.Bind(ServerAddress.UnixPipePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles an incoming connection
|
||||
/// </summary>
|
||||
/// <param name="listenSocket">Socket being used to listen on</param>
|
||||
/// <param name="status">Connection status</param>
|
||||
protected override void OnConnection(UvStreamHandle listenSocket, int status)
|
||||
{
|
||||
var acceptSocket = new UvPipeHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
listenSocket.Accept(acceptSocket);
|
||||
DispatchConnection(acceptSocket);
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
Log.LogError(0, ex, "PipeListener.OnConnection");
|
||||
acceptSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="ListenerPrimary"/> using UNIX sockets.
|
||||
/// </summary>
|
||||
public class PipeListenerPrimary : ListenerPrimary
|
||||
{
|
||||
public PipeListenerPrimary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the socket used to listen for incoming connections
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateListenSocket()
|
||||
{
|
||||
var socket = new UvPipeHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
socket.Bind(ServerAddress.UnixPipePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles an incoming connection
|
||||
/// </summary>
|
||||
/// <param name="listenSocket">Socket being used to listen on</param>
|
||||
/// <param name="status">Connection status</param>
|
||||
protected override void OnConnection(UvStreamHandle listenSocket, int status)
|
||||
{
|
||||
var acceptSocket = new UvPipeHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
listenSocket.Accept(acceptSocket);
|
||||
DispatchConnection(acceptSocket);
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
Log.LogError(0, ex, "ListenerPrimary.OnConnection");
|
||||
acceptSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="ListenerSecondary"/> using UNIX sockets.
|
||||
/// </summary>
|
||||
public class PipeListenerSecondary : ListenerSecondary
|
||||
{
|
||||
public PipeListenerSecondary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a socket which can be used to accept an incoming connection
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateAcceptSocket()
|
||||
{
|
||||
var acceptSocket = new UvPipeHandle(Log);
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
||||
return acceptSocket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="Listener"/> that uses TCP sockets as its transport.
|
||||
/// </summary>
|
||||
public class TcpListener : Listener
|
||||
{
|
||||
public TcpListener(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the socket used to listen for incoming connections
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateListenSocket()
|
||||
{
|
||||
var socket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
socket.NoDelay(ServerOptions.NoDelay);
|
||||
socket.Bind(ServerAddress);
|
||||
|
||||
// If requested port was "0", replace with assigned dynamic port.
|
||||
ServerAddress.Port = socket.GetSockIPEndPoint().Port;
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle an incoming connection
|
||||
/// </summary>
|
||||
/// <param name="listenSocket">Socket being used to listen on</param>
|
||||
/// <param name="status">Connection status</param>
|
||||
protected override void OnConnection(UvStreamHandle listenSocket, int status)
|
||||
{
|
||||
var acceptSocket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||
listenSocket.Accept(acceptSocket);
|
||||
DispatchConnection(acceptSocket);
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
Log.LogError(0, ex, "TcpListener.OnConnection");
|
||||
acceptSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="ListenerPrimary"/> using TCP sockets.
|
||||
/// </summary>
|
||||
public class TcpListenerPrimary : ListenerPrimary
|
||||
{
|
||||
public TcpListenerPrimary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the socket used to listen for incoming connections
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateListenSocket()
|
||||
{
|
||||
var socket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
socket.NoDelay(ServerOptions.NoDelay);
|
||||
socket.Bind(ServerAddress);
|
||||
|
||||
// If requested port was "0", replace with assigned dynamic port.
|
||||
ServerAddress.Port = socket.GetSockIPEndPoint().Port;
|
||||
}
|
||||
catch
|
||||
{
|
||||
socket.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles an incoming connection
|
||||
/// </summary>
|
||||
/// <param name="listenSocket">Socket being used to listen on</param>
|
||||
/// <param name="status">Connection status</param>
|
||||
protected override void OnConnection(UvStreamHandle listenSocket, int status)
|
||||
{
|
||||
var acceptSocket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||
listenSocket.Accept(acceptSocket);
|
||||
DispatchConnection(acceptSocket);
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
Log.LogError(0, ex, "TcpListenerPrimary.OnConnection");
|
||||
acceptSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="ListenerSecondary"/> using TCP sockets.
|
||||
/// </summary>
|
||||
public class TcpListenerSecondary : ListenerSecondary
|
||||
{
|
||||
public TcpListenerSecondary(ServiceContext serviceContext) : base(serviceContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a socket which can be used to accept an incoming connection
|
||||
/// </summary>
|
||||
protected override UvStreamHandle CreateAcceptSocket()
|
||||
{
|
||||
var acceptSocket = new UvTcpHandle(Log);
|
||||
acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
acceptSocket.NoDelay(ServerOptions.NoDelay);
|
||||
return acceptSocket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
/// </summary>
|
||||
public const string UnixPipeHostPrefix = "unix:/";
|
||||
|
||||
/// <summary>
|
||||
/// Prefix of host name used to specify pipe file descriptor in the configuration.
|
||||
/// </summary>
|
||||
public const string PipeDescriptorPrefix = "pipefd:";
|
||||
|
||||
/// <summary>
|
||||
/// Prefix of host name used to specify socket descriptor in the configuration.
|
||||
/// </summary>
|
||||
public const string SocketDescriptorPrefix = "sockfd:";
|
||||
|
||||
public const string ServerName = "Kestrel";
|
||||
|
||||
private static int? GetECONNRESET()
|
||||
|
|
|
|||
|
|
@ -61,49 +61,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
#endif
|
||||
}
|
||||
|
||||
public IDisposable CreateServer(ServerAddress address)
|
||||
public IDisposable CreateServer(ListenOptions listenOptions)
|
||||
{
|
||||
var listeners = new List<IAsyncDisposable>();
|
||||
|
||||
var usingPipes = address.IsUnixPipe;
|
||||
|
||||
try
|
||||
{
|
||||
var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
|
||||
var pipeMessage = Guid.NewGuid().ToByteArray();
|
||||
|
||||
var single = Threads.Count == 1;
|
||||
var first = true;
|
||||
|
||||
foreach (var thread in Threads)
|
||||
if (Threads.Count == 1)
|
||||
{
|
||||
if (single)
|
||||
{
|
||||
var listener = usingPipes ?
|
||||
(Listener) new PipeListener(ServiceContext) :
|
||||
new TcpListener(ServiceContext);
|
||||
listeners.Add(listener);
|
||||
listener.StartAsync(address, thread).Wait();
|
||||
}
|
||||
else if (first)
|
||||
{
|
||||
var listener = usingPipes
|
||||
? (ListenerPrimary) new PipeListenerPrimary(ServiceContext)
|
||||
: new TcpListenerPrimary(ServiceContext);
|
||||
var listener = new Listener(ServiceContext);
|
||||
listeners.Add(listener);
|
||||
listener.StartAsync(listenOptions, Threads[0]).Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
|
||||
var pipeMessage = Guid.NewGuid().ToByteArray();
|
||||
|
||||
listeners.Add(listener);
|
||||
listener.StartAsync(pipeName, pipeMessage, address, thread).Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
var listener = usingPipes
|
||||
? (ListenerSecondary) new PipeListenerSecondary(ServiceContext)
|
||||
: new TcpListenerSecondary(ServiceContext);
|
||||
listeners.Add(listener);
|
||||
listener.StartAsync(pipeName, pipeMessage, address, thread).Wait();
|
||||
}
|
||||
var listenerPrimary = new ListenerPrimary(ServiceContext);
|
||||
listeners.Add(listenerPrimary);
|
||||
listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, Threads[0]).Wait();
|
||||
|
||||
first = false;
|
||||
foreach (var thread in Threads.Skip(1))
|
||||
{
|
||||
var listenerSecondary = new ListenerSecondary(ServiceContext);
|
||||
listeners.Add(listenerSecondary);
|
||||
listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, thread).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
return new Disposable(() =>
|
||||
|
|
@ -114,7 +98,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
|
|||
catch
|
||||
{
|
||||
DisposeListeners(listeners);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
// 0000 0000 0b99 0017 => The third and fourth bytes 990B is the actual port
|
||||
// 9103 e000 9848 0120 => IPv6 address is represented in the 128bit field1 and field2.
|
||||
// 54a3 3e9d 2411 efb9 Read these two 64-bit long from right to left byte by byte.
|
||||
// 0000 0000 0000 0000
|
||||
// 0000 0000 0000 0010 => Scope ID 0x10 (eg [::1%16]) the first 4 bytes of field3 in host byte order.
|
||||
//
|
||||
// Example 2: 10.135.34.141:39178 when adopt dual-stack sockets, IPv4 is mapped to IPv6
|
||||
//
|
||||
|
|
@ -58,6 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
// Reference:
|
||||
// - Windows: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx
|
||||
// - Linux: https://github.com/torvalds/linux/blob/6a13feb9c82803e2b815eca72fa7a9f5561d7861/include/linux/socket.h
|
||||
// - Linux (sin6_scope_id): https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/net/sunrpc/addr.c#L82
|
||||
// - Apple: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/socket.h
|
||||
|
||||
// Quick calculate the port by mask the field and locate the byte 3 and byte 4
|
||||
|
|
@ -92,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
*((long*)(b + 8)) = _field2;
|
||||
}
|
||||
|
||||
return new IPEndPoint(new IPAddress(bytes), port);
|
||||
return new IPEndPoint(new IPAddress(bytes, scopeid: _field3 & 0xFFFFFFFF), port);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
|
|
@ -24,20 +25,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
_uv.tcp_init(loop, this);
|
||||
}
|
||||
|
||||
public void Bind(ServerAddress address)
|
||||
public void Open(IntPtr fileDescriptor)
|
||||
{
|
||||
var endpoint = CreateIPEndpoint(address);
|
||||
_uv.tcp_open(this, fileDescriptor);
|
||||
}
|
||||
|
||||
public void Bind(IPEndPoint endPoint)
|
||||
{
|
||||
SockAddr addr;
|
||||
var addressText = endpoint.Address.ToString();
|
||||
var addressText = endPoint.Address.ToString();
|
||||
|
||||
Exception error1;
|
||||
_uv.ip4_addr(addressText, endpoint.Port, out addr, out error1);
|
||||
_uv.ip4_addr(addressText, endPoint.Port, out addr, out error1);
|
||||
|
||||
if (error1 != null)
|
||||
{
|
||||
Exception error2;
|
||||
_uv.ip6_addr(addressText, endpoint.Port, out addr, out error2);
|
||||
_uv.ip6_addr(addressText, endPoint.Port, out addr, out error2);
|
||||
if (error2 != null)
|
||||
{
|
||||
throw error1;
|
||||
|
|
@ -65,38 +69,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
|
|||
return socketAddress.GetIPEndPoint();
|
||||
}
|
||||
|
||||
public void Open(IntPtr hSocket)
|
||||
{
|
||||
_uv.tcp_open(this, hSocket);
|
||||
}
|
||||
|
||||
public void NoDelay(bool enable)
|
||||
{
|
||||
_uv.tcp_nodelay(this, enable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="IPEndPoint"/> for the given host an port.
|
||||
/// If the host parameter isn't "localhost" or an IP address, use IPAddress.Any.
|
||||
/// </summary>
|
||||
public static IPEndPoint CreateIPEndpoint(ServerAddress address)
|
||||
{
|
||||
// TODO: IPv6 support
|
||||
IPAddress ip;
|
||||
|
||||
if (!IPAddress.TryParse(address.Host, out ip))
|
||||
{
|
||||
if (string.Equals(address.Host, "localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ip = IPAddress.Loopback;
|
||||
}
|
||||
else
|
||||
{
|
||||
ip = IPAddress.IPv6Any;
|
||||
}
|
||||
}
|
||||
|
||||
return new IPEndPoint(ip, address.Port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
|
@ -113,84 +114,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
engine.Start(threadCount);
|
||||
var atLeastOneListener = false;
|
||||
|
||||
foreach (var address in _serverAddresses.Addresses.ToArray())
|
||||
var listenOptions = Options.ListenOptions;
|
||||
|
||||
if (listenOptions.Any())
|
||||
{
|
||||
var parsedAddress = ServerAddress.FromUrl(address);
|
||||
atLeastOneListener = true;
|
||||
|
||||
if (!parsedAddress.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
var addresses = _serverAddresses.Addresses;
|
||||
if (addresses.SingleOrDefault() != "http://localhost:5000")
|
||||
{
|
||||
try
|
||||
{
|
||||
_disposables.Push(engine.CreateServer(parsedAddress));
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress}: address already in use.", ex);
|
||||
}
|
||||
var joined = string.Join(", ", addresses);
|
||||
throw new NotSupportedException($"Specifying address(es) '{joined}' is incompatible with also configuring endpoint(s) in UseKestrel.");
|
||||
}
|
||||
|
||||
throw;
|
||||
_serverAddresses.Addresses.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no endpoints are configured directly using KestrelServerOptions, use those configured via the IServerAddressesFeature.
|
||||
var copiedAddresses = _serverAddresses.Addresses.ToArray();
|
||||
_serverAddresses.Addresses.Clear();
|
||||
|
||||
foreach (var address in copiedAddresses)
|
||||
{
|
||||
var parsedAddress = ServerAddress.FromUrl(address);
|
||||
|
||||
if (parsedAddress.IsUnixPipe)
|
||||
{
|
||||
listenOptions.Add(new ListenOptions(parsedAddress.UnixPipePath)
|
||||
{
|
||||
Scheme = parsedAddress.Scheme,
|
||||
PathBase = parsedAddress.PathBase
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
||||
StartLocalhost(engine, parsedAddress);
|
||||
|
||||
// If StartLocalhost doesn't throw, there is at least one listener.
|
||||
// The port cannot change for "localhost".
|
||||
_serverAddresses.Addresses.Add(parsedAddress.ToString());
|
||||
atLeastOneListener = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// These endPoints will be added later to _serverAddresses.Addresses
|
||||
listenOptions.Add(new ListenOptions(CreateIPEndPoint(parsedAddress))
|
||||
{
|
||||
Scheme = parsedAddress.Scheme,
|
||||
PathBase = parsedAddress.PathBase
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
foreach (var endPoint in listenOptions)
|
||||
{
|
||||
atLeastOneListener = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (parsedAddress.Port == 0)
|
||||
_disposables.Push(engine.CreateServer(endPoint));
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new InvalidOperationException("Dynamic port binding is not supported when binding to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both.");
|
||||
throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
|
||||
}
|
||||
|
||||
var ipv4Address = parsedAddress.WithHost("127.0.0.1");
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
try
|
||||
{
|
||||
_disposables.Push(engine.CreateServer(ipv4Address));
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is UvException)
|
||||
{
|
||||
var uvEx = (UvException)ex.InnerException;
|
||||
if (uvEx.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress.ToString()} on the IPv4 loopback interface: port already in use.", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress.ToString()} on the IPv4 loopback interface: ({uvEx.Message})");
|
||||
exceptions.Add(uvEx);
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6Address = parsedAddress.WithHost("[::1]");
|
||||
|
||||
try
|
||||
{
|
||||
_disposables.Push(engine.CreateServer(ipv6Address));
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is UvException)
|
||||
{
|
||||
var uvEx = (UvException)ex.InnerException;
|
||||
if (uvEx.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress.ToString()} on the IPv6 loopback interface: port already in use.", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress.ToString()} on the IPv6 loopback interface: ({uvEx.Message})");
|
||||
exceptions.Add(uvEx);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count == 2)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress.ToString()}.", new AggregateException(exceptions));
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
// If requested port was "0", replace with assigned dynamic port.
|
||||
_serverAddresses.Addresses.Remove(address);
|
||||
_serverAddresses.Addresses.Add(parsedAddress.ToString());
|
||||
_serverAddresses.Addresses.Add(endPoint.ToString());
|
||||
}
|
||||
|
||||
if (!atLeastOneListener)
|
||||
|
|
@ -227,5 +226,84 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
$"Maximum request buffer size ({Options.Limits.MaxRequestBufferSize.Value}) must be greater than or equal to maximum request line size ({Options.Limits.MaxRequestLineSize}).");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartLocalhost(KestrelEngine engine, ServerAddress parsedAddress)
|
||||
{
|
||||
if (parsedAddress.Port == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Dynamic port binding is not supported when binding to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both.");
|
||||
}
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
try
|
||||
{
|
||||
var ipv4ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, parsedAddress.Port))
|
||||
{
|
||||
Scheme = parsedAddress.Scheme,
|
||||
PathBase = parsedAddress.PathBase
|
||||
};
|
||||
|
||||
_disposables.Push(engine.CreateServer(ipv4ListenOptions));
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is UvException)
|
||||
{
|
||||
var uvEx = (UvException)ex.InnerException;
|
||||
if (uvEx.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv4 loopback interface: port already in use.", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv4 loopback interface: ({uvEx.Message})");
|
||||
exceptions.Add(uvEx);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ipv6ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, parsedAddress.Port))
|
||||
{
|
||||
Scheme = parsedAddress.Scheme,
|
||||
PathBase = parsedAddress.PathBase
|
||||
};
|
||||
|
||||
_disposables.Push(engine.CreateServer(ipv6ListenOptions));
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is UvException)
|
||||
{
|
||||
var uvEx = (UvException)ex.InnerException;
|
||||
if (uvEx.StatusCode == Constants.EADDRINUSE)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv6 loopback interface: port already in use.", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv6 loopback interface: ({uvEx.Message})");
|
||||
exceptions.Add(uvEx);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count == 2)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress}.", new AggregateException(exceptions));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="IPEndPoint"/> for the given host an port.
|
||||
/// If the host parameter isn't "localhost" or an IP address, use IPAddress.Any.
|
||||
/// </summary>
|
||||
internal static IPEndPoint CreateIPEndPoint(ServerAddress address)
|
||||
{
|
||||
IPAddress ip;
|
||||
|
||||
if (!IPAddress.TryParse(address.Host, out ip))
|
||||
{
|
||||
ip = IPAddress.IPv6Any;
|
||||
}
|
||||
|
||||
return new IPEndPoint(ip, address.Port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
||||
{
|
||||
|
|
@ -8,6 +12,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
/// </summary>
|
||||
public class KestrelServerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the endpoints that Kestrel should listen to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this list is empty, the server.urls setting (e.g. UseUrls) is used.
|
||||
/// </remarks>
|
||||
internal List<ListenOptions> ListenOptions { get; } = new List<ListenOptions>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <c>Server</c> header should be included in each response.
|
||||
/// </summary>
|
||||
|
|
@ -17,22 +29,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
public bool AddServerHeader { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the UseKestrel options callback to resolve and use services registered by the application during startup.
|
||||
/// Enables the Listen options callback to resolve and use services registered by the application during startup.
|
||||
/// Typically initialized by <see cref="Hosting.WebHostBuilderKestrelExtensions.UseKestrel(Hosting.IWebHostBuilder, Action{KestrelServerOptions})"/>.
|
||||
/// </summary>
|
||||
public IServiceProvider ApplicationServices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an <see cref="IConnectionFilter"/> that allows each connection <see cref="System.IO.Stream"/>
|
||||
/// to be intercepted and transformed.
|
||||
/// Configured by the <c>UseHttps()</c> and <see cref="Hosting.KestrelServerOptionsConnectionLoggingExtensions.UseConnectionLogging(KestrelServerOptions)"/>
|
||||
/// extension methods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to null.
|
||||
/// </remarks>
|
||||
public IConnectionFilter ConnectionFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This property is obsolete and will be removed in a future version.
|
||||
|
|
@ -64,14 +65,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
/// </summary>
|
||||
public KestrelServerLimits Limits { get; } = new KestrelServerLimits();
|
||||
|
||||
/// <summary>
|
||||
/// Set to false to enable Nagle's algorithm for all connections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to true.
|
||||
/// </remarks>
|
||||
public bool NoDelay { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time after the server begins shutting down before connections will be forcefully closed.
|
||||
/// Kestrel will wait for the duration of the timeout for any ongoing request processing to complete before
|
||||
|
|
@ -115,5 +108,111 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
return threadCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given IP address and port.
|
||||
/// </summary>
|
||||
public void Listen(IPAddress address, int port)
|
||||
{
|
||||
Listen(address, port, _ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given IP address and port.
|
||||
/// The callback configures endpoint-specific settings.
|
||||
/// </summary>
|
||||
public void Listen(IPAddress address, int port, Action<ListenOptions> configure)
|
||||
{
|
||||
if (address == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
}
|
||||
|
||||
Listen(new IPEndPoint(address, port), configure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given IP endpoint.
|
||||
/// </summary>
|
||||
public void Listen(IPEndPoint endPoint)
|
||||
{
|
||||
Listen(endPoint, _ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given IP address and port.
|
||||
/// The callback configures endpoint-specific settings.
|
||||
/// </summary>
|
||||
public void Listen(IPEndPoint endPoint, Action<ListenOptions> configure)
|
||||
{
|
||||
if (endPoint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endPoint));
|
||||
}
|
||||
if (configure == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
var listenOptions = new ListenOptions(endPoint) { KestrelServerOptions = this };
|
||||
configure(listenOptions);
|
||||
ListenOptions.Add(listenOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given Unix domain socket path.
|
||||
/// </summary>
|
||||
public void ListenUnixSocket(string socketPath)
|
||||
{
|
||||
ListenUnixSocket(socketPath, _ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind to given Unix domain socket path.
|
||||
/// Specify callback to configure endpoint-specific settings.
|
||||
/// </summary>
|
||||
public void ListenUnixSocket(string socketPath, Action<ListenOptions> configure)
|
||||
{
|
||||
if (socketPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(socketPath));
|
||||
}
|
||||
if (socketPath.Length == 0 || socketPath[0] != '/')
|
||||
{
|
||||
throw new ArgumentException("Unix socket path must be absolute.", nameof(socketPath));
|
||||
}
|
||||
if (configure == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
var listenOptions = new ListenOptions(socketPath) { KestrelServerOptions = this };
|
||||
configure(listenOptions);
|
||||
ListenOptions.Add(listenOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a socket file descriptor.
|
||||
/// </summary>
|
||||
public void ListenHandle(ulong handle)
|
||||
{
|
||||
ListenHandle(handle, _ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a socket file descriptor.
|
||||
/// The callback configures endpoint-specific settings.
|
||||
/// </summary>
|
||||
public void ListenHandle(ulong handle, Action<ListenOptions> configure)
|
||||
{
|
||||
if (configure == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
var listenOptions = new ListenOptions(handle) { KestrelServerOptions = this };
|
||||
configure(listenOptions);
|
||||
ListenOptions.Add(listenOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
// 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.Net;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor for an already open
|
||||
/// socket that Kestrel should bind to or open.
|
||||
/// </summary>
|
||||
public class ListenOptions
|
||||
{
|
||||
internal ListenOptions(IPEndPoint endPoint)
|
||||
{
|
||||
Type = ListenType.IPEndPoint;
|
||||
IPEndPoint = endPoint;
|
||||
}
|
||||
|
||||
internal ListenOptions(string socketPath)
|
||||
{
|
||||
Type = ListenType.SocketPath;
|
||||
SocketPath = socketPath;
|
||||
}
|
||||
|
||||
internal ListenOptions(ulong fileHandle)
|
||||
{
|
||||
Type = ListenType.FileHandle;
|
||||
FileHandle = fileHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of interface being described: either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor.
|
||||
/// </summary>
|
||||
public ListenType Type { get; }
|
||||
|
||||
// IPEndPoint is mutable so port 0 can be updated to the bound port.
|
||||
/// <summary>
|
||||
/// The <see cref="IPEndPoint"/> to bind to.
|
||||
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.IPEndPoint"/>.
|
||||
/// </summary>
|
||||
public IPEndPoint IPEndPoint { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute path to a Unix domain socket to bind to.
|
||||
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.SocketPath"/>.
|
||||
/// </summary>
|
||||
public string SocketPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A file descriptor for the socket to open.
|
||||
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
|
||||
/// </summary>
|
||||
public ulong FileHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables an <see cref="IConnectionAdapter"/> to resolve and use services registered by the application during startup.
|
||||
/// Only set if accessed from the callback of a <see cref="KestrelServerOptions"/> Listen* method.
|
||||
/// </summary>
|
||||
public KestrelServerOptions KestrelServerOptions { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to false to enable Nagle's algorithm for all connections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to true.
|
||||
/// </remarks>
|
||||
public bool NoDelay { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="List{IConnectionAdapter}"/> that allows each connection <see cref="System.IO.Stream"/>
|
||||
/// to be intercepted and transformed.
|
||||
/// Configured by the <c>UseHttps()</c> and <see cref="Hosting.ListenOptionsConnectionLoggingExtensions.UseConnectionLogging(ListenOptions)"/>
|
||||
/// extension methods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to empty.
|
||||
/// </remarks>
|
||||
public List<IConnectionAdapter> ConnectionAdapters { get; } = new List<IConnectionAdapter>();
|
||||
|
||||
// PathBase and Scheme are hopefully only a temporary measure for back compat with IServerAddressesFeature.
|
||||
// This allows a ListenOptions to describe all the information encoded in IWebHostBuilder.UseUrls.
|
||||
internal string PathBase { get; set; }
|
||||
internal string Scheme { get; set; } = "http";
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// Use http scheme for all addresses. If https should be used for this endPoint,
|
||||
// it can still be configured for this endPoint specifically.
|
||||
switch (Type)
|
||||
{
|
||||
case ListenType.IPEndPoint:
|
||||
return $"{Scheme}://{IPEndPoint}{PathBase}";
|
||||
case ListenType.SocketPath:
|
||||
// ":" is used by ServerAddress to separate the socket path from PathBase.
|
||||
return $"{Scheme}://unix:{SocketPath}:{PathBase}";
|
||||
case ListenType.FileHandle:
|
||||
// This was never supported via --server.urls, so no need to include Scheme or PathBase.
|
||||
return "http://<file handle>";
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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.AspNetCore.Server.Kestrel
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the <see cref="ListenOptions"/> types.
|
||||
/// </summary>
|
||||
public enum ListenType
|
||||
{
|
||||
IPEndPoint,
|
||||
SocketPath,
|
||||
FileHandle
|
||||
}
|
||||
}
|
||||
|
|
@ -169,4 +169,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class KesterlServerOptionsSystemdExtensions
|
||||
{
|
||||
// SD_LISTEN_FDS_START https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
|
||||
private const ulong SdListenFdsStart = 3;
|
||||
private const string ListenPidEnvVar = "LISTEN_PID";
|
||||
|
||||
/// <summary>
|
||||
/// Open file descriptor (SD_LISTEN_FDS_START) initialized by systemd socket-based activation logic if available.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="KestrelServerOptions"/>.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseSystemd(this KestrelServerOptions options)
|
||||
{
|
||||
return options.UseSystemd(_ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open file descriptor (SD_LISTEN_FDS_START) initialized by systemd socket-based activation logic if available.
|
||||
/// Specify callback to configure endpoint-specific settings.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="KestrelServerOptions"/>.
|
||||
/// </returns>
|
||||
public static KestrelServerOptions UseSystemd(this KestrelServerOptions options, Action<ListenOptions> configure)
|
||||
{
|
||||
if (string.Equals(Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture), Environment.GetEnvironmentVariable(ListenPidEnvVar), StringComparison.Ordinal))
|
||||
{
|
||||
options.ListenHandle(SdListenFdsStart, configure);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsConnectionLoggingExtensions",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.ConnectionFilterContext",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.LoggingConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.NoOpConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter get_ConnectionFilter()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Void set_ConnectionFilter(Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter value)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Boolean get_NoDelay()",
|
||||
"NewTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.ListenOptions",
|
||||
"NewMemberId": "public System.Boolean get_NoDelay()",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Void set_NoDelay(System.Boolean value)",
|
||||
"NewTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.ListenOptions",
|
||||
"NewMemberId": "public System.Void set_NoDelay(System.Boolean value)",
|
||||
"Kind": "Modification"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
[
|
||||
{
|
||||
"OldTypeId": "public static class Microsoft.AspNetCore.Hosting.KestrelServerOptionsConnectionLoggingExtensions",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.ConnectionFilterContext",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public interface Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.LoggingConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.Filter.NoOpConnectionFilter : Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter get_ConnectionFilter()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Void set_ConnectionFilter(Microsoft.AspNetCore.Server.Kestrel.Filter.IConnectionFilter value)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Boolean get_NoDelay()",
|
||||
"NewTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.ListenOptions",
|
||||
"NewMemberId": "public System.Boolean get_NoDelay()",
|
||||
"Kind": "Modification"
|
||||
},
|
||||
{
|
||||
"OldTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions",
|
||||
"OldMemberId": "public System.Void set_NoDelay(System.Boolean value)",
|
||||
"NewTypeId": "public class Microsoft.AspNetCore.Server.Kestrel.ListenOptions",
|
||||
"NewMemberId": "public System.Void set_NoDelay(System.Boolean value)",
|
||||
"Kind": "Modification"
|
||||
}
|
||||
]
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
},
|
||||
"netstandard1.3": {
|
||||
"dependencies": {
|
||||
"System.Diagnostics.Process": "4.4.0-*",
|
||||
"System.Threading.Thread": "4.4.0-*",
|
||||
"System.Threading.ThreadPool": "4.4.0-*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
public class AddressRegistrationTests
|
||||
{
|
||||
[Theory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(AddressRegistrationDataIPv4))]
|
||||
[Theory, MemberData(nameof(AddressRegistrationDataIPv4))]
|
||||
public async Task RegisterAddresses_IPv4_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
{
|
||||
await RegisterAddresses_Success(addressInput, testUrls);
|
||||
|
|
@ -35,14 +36,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
await RegisterAddresses_Success(addressInput, testUrls);
|
||||
}
|
||||
|
||||
[ConditionalTheory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(AddressRegistrationDataIPv4Port443))]
|
||||
[PortSupportedCondition(443)]
|
||||
public async Task RegisterAddresses_IPv4Port443_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
[Theory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(IPEndPointRegistrationDataRandomPort))]
|
||||
[IPv6SupportedCondition]
|
||||
public async Task RegisterIPEndPoint_RandomPort_Success(IPEndPoint endPoint, Func<IPEndPoint, string> testUrl)
|
||||
{
|
||||
await RegisterAddresses_Success(addressInput, testUrls);
|
||||
await RegisterIPEndPoint_Success(endPoint, testUrl);
|
||||
}
|
||||
|
||||
[ConditionalTheory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(AddressRegistrationDataIPv6))]
|
||||
[ConditionalTheory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(IPEndPointRegistrationDataPort443))]
|
||||
[IPv6SupportedCondition]
|
||||
[PortSupportedCondition(443)]
|
||||
public async Task RegisterIPEndPoint_Port443_Success(IPEndPoint endpoint, Func<IPEndPoint, string> testUrl)
|
||||
{
|
||||
await RegisterIPEndPoint_Success(endpoint, testUrl);
|
||||
}
|
||||
|
||||
[ConditionalTheory, MemberData(nameof(AddressRegistrationDataIPv6))]
|
||||
[IPv6SupportedCondition]
|
||||
public async Task RegisterAddresses_IPv6_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
{
|
||||
|
|
@ -57,15 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
await RegisterAddresses_Success(addressInput, testUrls);
|
||||
}
|
||||
|
||||
[ConditionalTheory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(AddressRegistrationDataIPv6Port443))]
|
||||
[IPv6SupportedCondition]
|
||||
[PortSupportedCondition(443)]
|
||||
public async Task RegisterAddresses_IPv6Port443_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
{
|
||||
await RegisterAddresses_Success(addressInput, testUrls);
|
||||
}
|
||||
|
||||
[ConditionalTheory(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)"), MemberData(nameof(AddressRegistrationDataIPv6ScopeId))]
|
||||
[ConditionalTheory, MemberData(nameof(AddressRegistrationDataIPv6ScopeId))]
|
||||
[IPv6SupportedCondition]
|
||||
public async Task RegisterAddresses_IPv6ScopeId_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
{
|
||||
|
|
@ -75,10 +76,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
private async Task RegisterAddresses_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
|
||||
{
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseUrls(addressInput)
|
||||
.Configure(ConfigureEchoAddress);
|
||||
|
||||
|
|
@ -97,6 +95,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task RegisterIPEndPoint_Success(IPEndPoint endPoint, Func<IPEndPoint, string> testUrl)
|
||||
{
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(endPoint, listenOptions =>
|
||||
{
|
||||
if (testUrl(listenOptions.IPEndPoint).StartsWith("https"))
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
}
|
||||
});
|
||||
})
|
||||
.Configure(ConfigureEchoAddress);
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
host.Start();
|
||||
|
||||
var options = ((IOptions<KestrelServerOptions>)host.Services.GetService(typeof(IOptions<KestrelServerOptions>))).Value;
|
||||
Assert.Single(options.ListenOptions);
|
||||
var listenOptions = options.ListenOptions[0];
|
||||
|
||||
var response = await HttpClientSlim.GetStringAsync(testUrl(listenOptions.IPEndPoint), validateCertificate: false);
|
||||
|
||||
// Compare the response with Uri.ToString(), rather than testUrl directly.
|
||||
// Required to handle IPv6 addresses with zone index, like "fe80::3%1"
|
||||
Assert.Equal(new Uri(testUrl(listenOptions.IPEndPoint)).ToString(), response);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenBindingToIPv4AddressInUse()
|
||||
{
|
||||
|
|
@ -200,37 +229,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
dataset.Add(string.Empty, _ => new[] { "http://127.0.0.1:5000/" });
|
||||
|
||||
// Static ports
|
||||
var port1 = GetNextPort();
|
||||
var port2 = GetNextPort();
|
||||
var port = GetNextPort();
|
||||
|
||||
// Loopback
|
||||
dataset.Add($"http://127.0.0.1:{port1};https://127.0.0.1:{port2}",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"https://127.0.0.1:{port2}/" });
|
||||
dataset.Add($"http://127.0.0.1:{port}", _ => new[] { $"http://127.0.0.1:{port}/" });
|
||||
|
||||
// Localhost
|
||||
dataset.Add($"http://localhost:{port1};https://localhost:{port2}",
|
||||
_ => new[] { $"http://localhost:{port1}/", $"http://127.0.0.1:{port1}/",
|
||||
$"https://localhost:{port2}/", $"https://127.0.0.1:{port2}/" });
|
||||
dataset.Add($"http://localhost:{port}", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/" });
|
||||
|
||||
// Any
|
||||
dataset.Add($"http://*:{port1}/;https://*:{port2}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"https://127.0.0.1:{port2}/" });
|
||||
dataset.Add($"http://+:{port1}/;https://+:{port2}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"https://127.0.0.1:{port2}/" });
|
||||
dataset.Add($"http://*:{port}/", _ => new[] { $"http://127.0.0.1:{port}/" });
|
||||
dataset.Add($"http://+:{port}/", _ => new[] { $"http://127.0.0.1:{port}/" });
|
||||
|
||||
// Path after port
|
||||
dataset.Add($"http://127.0.0.1:{port1}/base/path;https://127.0.0.1:{port2}/base/path",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/base/path", $"https://127.0.0.1:{port2}/base/path" });
|
||||
dataset.Add($"http://127.0.0.1:{port}/base/path", _ => new[] { $"http://127.0.0.1:{port}/base/path" });
|
||||
|
||||
// Dynamic port and non-loopback addresses
|
||||
dataset.Add("http://127.0.0.1:0/;https://127.0.0.1:0/", GetTestUrls);
|
||||
dataset.Add($"http://{Dns.GetHostName()}:0/;https://{Dns.GetHostName()}:0/", GetTestUrls);
|
||||
dataset.Add("http://127.0.0.1:0/", GetTestUrls);
|
||||
dataset.Add($"http://{Dns.GetHostName()}:0/", GetTestUrls);
|
||||
|
||||
var ipv4Addresses = GetIPAddresses()
|
||||
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);
|
||||
foreach (var ip in ipv4Addresses)
|
||||
{
|
||||
dataset.Add($"http://{ip}:0/;https://{ip}:0/", GetTestUrls);
|
||||
dataset.Add($"http://{ip}:0/", GetTestUrls);
|
||||
}
|
||||
|
||||
return dataset;
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<IPEndPoint, Func<IPEndPoint, string>> IPEndPointRegistrationDataRandomPort
|
||||
{
|
||||
get
|
||||
{
|
||||
var dataset = new TheoryData<IPEndPoint, Func<IPEndPoint, string>>();
|
||||
|
||||
// Static port
|
||||
var port = GetNextPort();
|
||||
|
||||
// Loopback
|
||||
dataset.Add(new IPEndPoint(IPAddress.Loopback, port), _ => $"http://127.0.0.1:{port}/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.Loopback, port), _ => $"https://127.0.0.1:{port}/");
|
||||
|
||||
// IPv6 loopback
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Loopback, port), _ => FixTestUrl($"http://[::1]:{port}/"));
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Loopback, port), _ => FixTestUrl($"https://[::1]:{port}/"));
|
||||
|
||||
// Any
|
||||
dataset.Add(new IPEndPoint(IPAddress.Any, port), _ => $"http://127.0.0.1:{port}/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.Any, port), _ => $"https://127.0.0.1:{port}/");
|
||||
|
||||
// IPv6 Any
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Any, port), _ => $"http://127.0.0.1:{port}/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Any, port), _ => FixTestUrl($"http://[::1]:{port}/"));
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Any, port), _ => $"https://127.0.0.1:{port}/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Any, port), _ => FixTestUrl($"https://[::1]:{port}/"));
|
||||
|
||||
// Dynamic port
|
||||
dataset.Add(new IPEndPoint(IPAddress.Loopback, 0), endPoint => $"http://127.0.0.1:{endPoint.Port}/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.Loopback, 0), endPoint => $"https://127.0.0.1:{endPoint.Port}/");
|
||||
|
||||
var ipv4Addresses = GetIPAddresses()
|
||||
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);
|
||||
foreach (var ip in ipv4Addresses)
|
||||
{
|
||||
dataset.Add(new IPEndPoint(ip, 0), endPoint => FixTestUrl($"http://{endPoint}/"));
|
||||
dataset.Add(new IPEndPoint(ip, 0), endPoint => FixTestUrl($"https://{endPoint}/"));
|
||||
}
|
||||
|
||||
return dataset;
|
||||
|
|
@ -252,16 +317,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv4Port443
|
||||
public static TheoryData<IPEndPoint, Func<IPEndPoint, string>> IPEndPointRegistrationDataPort443
|
||||
{
|
||||
get
|
||||
{
|
||||
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
|
||||
var dataset = new TheoryData<IPEndPoint, Func<IPEndPoint, string>>();
|
||||
|
||||
// Default port for HTTPS (443)
|
||||
dataset.Add("https://127.0.0.1", _ => new[] { "https://127.0.0.1/" });
|
||||
dataset.Add("https://localhost", _ => new[] { "https://127.0.0.1/" });
|
||||
dataset.Add("https://*", _ => new[] { "https://127.0.0.1/" });
|
||||
dataset.Add(new IPEndPoint(IPAddress.Loopback, 443), _ => "https://127.0.0.1/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Loopback, 443), _ => FixTestUrl("https://[::1]/"));
|
||||
dataset.Add(new IPEndPoint(IPAddress.Any, 443), _ => "https://127.0.0.1/");
|
||||
dataset.Add(new IPEndPoint(IPAddress.IPv6Any, 443), _ => FixTestUrl("https://[::1]/"));
|
||||
|
||||
return dataset;
|
||||
}
|
||||
|
|
@ -278,34 +343,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
dataset.Add(string.Empty, _ => new[] { "http://127.0.0.1:5000/", "http://[::1]:5000/" });
|
||||
|
||||
// Static ports
|
||||
var port1 = GetNextPort();
|
||||
var port2 = GetNextPort();
|
||||
var port = GetNextPort();
|
||||
|
||||
// Loopback
|
||||
dataset.Add($"http://[::1]:{port1}/;https://[::1]:{port2}/",
|
||||
_ => new[] { $"http://[::1]:{port1}/", $"https://[::1]:{port2}/" });
|
||||
dataset.Add($"http://[::1]:{port}/",
|
||||
_ => new[] { $"http://[::1]:{port}/" });
|
||||
|
||||
// Localhost
|
||||
dataset.Add($"http://localhost:{port1};https://localhost:{port2}",
|
||||
_ => new[] { $"http://localhost:{port1}/", $"http://127.0.0.1:{port1}/", $"http://[::1]:{port1}/",
|
||||
$"https://localhost:{port2}/", $"https://127.0.0.1:{port2}/", $"https://[::1]:{port2}/" });
|
||||
dataset.Add($"http://localhost:{port}",
|
||||
_ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
|
||||
|
||||
// Any
|
||||
dataset.Add($"http://*:{port1}/;https://*:{port2}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"http://[::1]:{port1}/",
|
||||
$"https://127.0.0.1:{port2}/", $"https://[::1]:{port2}/" });
|
||||
dataset.Add($"http://+:{port1}/;https://+:{port2}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"http://[::1]:{port1}/",
|
||||
$"https://127.0.0.1:{port2}/", $"https://[::1]:{port2}/" });
|
||||
dataset.Add($"http://*:{port}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
|
||||
dataset.Add($"http://+:{port}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
|
||||
|
||||
// Explicit IPv4 and IPv6 on same port
|
||||
dataset.Add($"http://127.0.0.1:{port1}/;http://[::1]:{port1}/;https://127.0.0.1:{port2}/;https://[::1]:{port2}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port1}/", $"http://[::1]:{port1}/",
|
||||
$"https://127.0.0.1:{port2}/", $"https://[::1]:{port2}/" });
|
||||
dataset.Add($"http://127.0.0.1:{port}/;http://[::1]:{port}/",
|
||||
_ => new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
|
||||
|
||||
// Path after port
|
||||
dataset.Add($"http://[::1]:{port1}/base/path;https://[::1]:{port2}/base/path",
|
||||
_ => new[] { $"http://[::1]:{port1}/base/path", $"https://[::1]:{port2}/base/path" });
|
||||
dataset.Add($"http://[::1]:{port}/base/path",
|
||||
_ => new[] { $"http://[::1]:{port}/base/path" });
|
||||
|
||||
// Dynamic port and non-loopback addresses
|
||||
var ipv6Addresses = GetIPAddresses()
|
||||
|
|
@ -313,7 +373,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
.Where(ip => ip.ScopeId == 0);
|
||||
foreach (var ip in ipv6Addresses)
|
||||
{
|
||||
dataset.Add($"http://[{ip}]:0/;https://[{ip}]:0/", GetTestUrls);
|
||||
dataset.Add($"http://[{ip}]:0/", GetTestUrls);
|
||||
}
|
||||
|
||||
return dataset;
|
||||
|
|
@ -335,21 +395,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv6Port443
|
||||
{
|
||||
get
|
||||
{
|
||||
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
|
||||
|
||||
// Default port for HTTPS (443)
|
||||
dataset.Add("https://[::1]", _ => new[] { "https://[::1]/" });
|
||||
dataset.Add("https://localhost", _ => new[] { "https://127.0.0.1/", "https://[::1]/" });
|
||||
dataset.Add("https://*", _ => new[] { "https://[::1]/" });
|
||||
|
||||
return dataset;
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv6ScopeId
|
||||
{
|
||||
get
|
||||
|
|
@ -362,7 +407,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
.Where(ip => ip.ScopeId != 0);
|
||||
foreach (var ip in ipv6Addresses)
|
||||
{
|
||||
dataset.Add($"http://[{ip}]:0/;https://[{ip}]:0/", GetTestUrls);
|
||||
dataset.Add($"http://[{ip}]:0/", GetTestUrls);
|
||||
}
|
||||
|
||||
return dataset;
|
||||
|
|
@ -380,11 +425,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
private static string[] GetTestUrls(IServerAddressesFeature addressesFeature)
|
||||
{
|
||||
return addressesFeature.Addresses
|
||||
.Select(a => a.Replace("://+", "://localhost"))
|
||||
.Select(a => a.EndsWith("/") ? a : a + "/")
|
||||
.Select(FixTestUrl)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string FixTestUrl(string url)
|
||||
{
|
||||
var fixedUrl = url.Replace("://+", "://localhost")
|
||||
.Replace("0.0.0.0", Dns.GetHostName())
|
||||
.Replace("[::]", Dns.GetHostName());
|
||||
|
||||
if (!fixedUrl.EndsWith("/"))
|
||||
{
|
||||
fixedUrl = fixedUrl + "/";
|
||||
}
|
||||
|
||||
return fixedUrl;
|
||||
}
|
||||
|
||||
private void ConfigureEchoAddress(IApplicationBuilder app)
|
||||
{
|
||||
app.Run(context =>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
{
|
||||
using (var host = StartHost(protocol: "https"))
|
||||
{
|
||||
Assert.Equal("test", await HttpClientSlim.GetStringAsync(host.GetUri(), validateCertificate: false));
|
||||
Assert.Equal("test", await HttpClientSlim.GetStringAsync(host.GetUri(isHttps: true), validateCertificate: false));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
using (var host = StartHost(protocol: "https",
|
||||
handler: (context) => context.Request.Body.CopyToAsync(context.Response.Body)))
|
||||
{
|
||||
Assert.Equal("test post", await HttpClientSlim.PostAsync(host.GetUri(),
|
||||
Assert.Equal("test post", await HttpClientSlim.PostAsync(host.GetUri(isHttps: true),
|
||||
new StringContent("test post"), validateCertificate: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -77,10 +78,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
private IWebHost StartHost(string protocol = "http", int statusCode = 200, Func<HttpContext, Task> handler = null)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseUrls($"{protocol}://127.0.0.1:0")
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
if (protocol == "https")
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
}
|
||||
});
|
||||
})
|
||||
.Configure((app) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.UseUrls("https://127.0.0.1:0/")
|
||||
.UseLoggerFactory(loggerFactory)
|
||||
.Configure(app => { });
|
||||
|
||||
|
|
@ -61,9 +63,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.UseUrls("https://127.0.0.1:0/")
|
||||
.UseLoggerFactory(loggerFactory)
|
||||
.Configure(app => { });
|
||||
|
||||
|
|
@ -90,12 +94,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort()
|
||||
{
|
||||
var x509Certificate2 = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword");
|
||||
var x509Certificate2 = new X509Certificate2("TestResources/testCert.pfx", "testPassword");
|
||||
var loggerFactory = new HandshakeErrorLoggerFactory();
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.UseUrls("https://127.0.0.1:0/")
|
||||
.UseLoggerFactory(loggerFactory)
|
||||
|
|
@ -141,14 +148,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
var x509Certificate2 = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword");
|
||||
var x509Certificate2 = new X509Certificate2("TestResources/testCert.pfx", "testPassword");
|
||||
var loggerFactory = new HandshakeErrorLoggerFactory();
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.UseUrls("https://127.0.0.1:0/")
|
||||
.UseLoggerFactory(loggerFactory)
|
||||
.Configure(app => app.Run(async httpContext =>
|
||||
{
|
||||
|
|
@ -194,9 +203,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var hostBuilder = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.UseUrls("https://127.0.0.1:0/")
|
||||
.UseLoggerFactory(loggerFactory)
|
||||
.Configure(app => { });
|
||||
|
||||
|
|
@ -221,7 +232,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
if (categoryName == nameof(HttpsConnectionFilter))
|
||||
if (categoryName == nameof(HttpsConnectionAdapter))
|
||||
{
|
||||
return FilterLogger;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
{
|
||||
public static class IWebHostPortExtensions
|
||||
{
|
||||
public static string GetHost(this IWebHost host)
|
||||
public static string GetHost(this IWebHost host, bool isHttps = false)
|
||||
{
|
||||
return host.GetUri().Host;
|
||||
return host.GetUri(isHttps).Host;
|
||||
}
|
||||
|
||||
public static int GetPort(this IWebHost host)
|
||||
|
|
@ -37,13 +37,29 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
public static IEnumerable<Uri> GetUris(this IWebHost host)
|
||||
{
|
||||
return host.ServerFeatures.Get<IServerAddressesFeature>().Addresses
|
||||
.Select(a => a.Replace("://+", "://localhost"))
|
||||
.Select(a => new Uri(a));
|
||||
}
|
||||
|
||||
public static Uri GetUri(this IWebHost host)
|
||||
public static Uri GetUri(this IWebHost host, bool isHttps = false)
|
||||
{
|
||||
return host.GetUris().First();
|
||||
var uri = host.GetUris().First();
|
||||
|
||||
if (isHttps && uri.Scheme == "http")
|
||||
{
|
||||
var uriBuilder = new UriBuilder(uri)
|
||||
{
|
||||
Scheme = "https",
|
||||
};
|
||||
|
||||
if (uri.Port == 80)
|
||||
{
|
||||
uriBuilder.Port = 443;
|
||||
}
|
||||
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -11,18 +12,20 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||
{
|
||||
public class LoggingConnectionFilterTests
|
||||
public class LoggingConnectionAdapterTests
|
||||
{
|
||||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task LoggingConnectionFilterCanBeAddedBeforeAndAfterHttpsFilter()
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseUrls($"https://127.0.0.1:0")
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.UseConnectionLogging();
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
})
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
listenOptions.UseConnectionLogging();
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
});
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(context =>
|
||||
|
|
@ -82,11 +82,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
var clientFinishedSendingRequestBody = new ManualResetEvent(false);
|
||||
var lastBytesWritten = DateTime.MaxValue;
|
||||
|
||||
using (var host = StartWebHost(maxRequestBufferSize, data, startReadingRequestBody, clientFinishedSendingRequestBody))
|
||||
using (var host = StartWebHost(maxRequestBufferSize, data, ssl, startReadingRequestBody, clientFinishedSendingRequestBody))
|
||||
{
|
||||
var port = host.GetPort(ssl ? "https" : "http");
|
||||
var port = host.GetPort();
|
||||
using (var socket = CreateSocket(port))
|
||||
using (var stream = await CreateStreamAsync(socket, ssl, host.GetHost()))
|
||||
using (var stream = await CreateStreamAsync(socket, ssl, host.GetHost(ssl)))
|
||||
{
|
||||
await WritePostRequestHeaders(stream, data.Length);
|
||||
|
||||
|
|
@ -161,14 +161,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private static IWebHost StartWebHost(long? maxRequestBufferSize, byte[] expectedBody, ManualResetEvent startReadingRequestBody,
|
||||
private static IWebHost StartWebHost(long? maxRequestBufferSize, byte[] expectedBody, bool useSsl, ManualResetEvent startReadingRequestBody,
|
||||
ManualResetEvent clientFinishedSendingRequestBody)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
|
||||
{
|
||||
if (useSsl)
|
||||
{
|
||||
listenOptions.UseHttps("TestResources/testCert.pfx", "testPassword");
|
||||
}
|
||||
});
|
||||
|
||||
options.Limits.MaxRequestBufferSize = maxRequestBufferSize;
|
||||
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
|
||||
|
||||
if (maxRequestBufferSize.HasValue &&
|
||||
maxRequestBufferSize.Value < options.Limits.MaxRequestLineSize)
|
||||
|
|
@ -176,7 +183,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
options.Limits.MaxRequestLineSize = (int)maxRequestBufferSize;
|
||||
}
|
||||
})
|
||||
.UseUrls("http://127.0.0.1:0/", "https://127.0.0.1:0/")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.Configure(app => app.Run(async context =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
disposedTcs.TrySetResult(c.Response.StatusCode);
|
||||
});
|
||||
|
||||
using (var server = new TestServer(handler, new TestServiceContext(), "http://127.0.0.1:0", mockHttpContextFactory.Object))
|
||||
using (var server = new TestServer(handler, new TestServiceContext(), mockHttpContextFactory.Object))
|
||||
{
|
||||
if (!sendMalformedRequest)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
FROM microsoft/dotnet:1.1-runtime-deps
|
||||
|
||||
# The "container" environment variable is read by systemd.
|
||||
ENV container=docker
|
||||
|
||||
# This is required by systemd and won't work without "dotnet run --privileged".
|
||||
VOLUME ["/sys/fs/cgroup"]
|
||||
|
||||
# Create activate-kestrel.service to launch the "publish" app on new requests to 8080.
|
||||
EXPOSE 8080
|
||||
ADD .dotnet/ /usr/share/dotnet/
|
||||
ADD publish/ /publish/
|
||||
ADD activate-kestrel.socket /etc/systemd/system/activate-kestrel.socket
|
||||
ADD activate-kestrel.service /etc/systemd/system/activate-kestrel.service
|
||||
|
||||
# Install and configure systemd which requires dbus for graceful shutdown.
|
||||
RUN ["apt-get", "-o", "Acquire::Check-Valid-Until=false", "update"]
|
||||
RUN ["apt-get", "install", "-y", "dbus"]
|
||||
RUN ["systemctl", "mask", "getty.target", "console-getty.service"]
|
||||
RUN ["cp", "/lib/systemd/system/dbus.service", "/etc/systemd/system/"]
|
||||
RUN ["sed", "-i", "s/OOMScoreAdjust=-900//", "/etc/systemd/system/dbus.service"]
|
||||
|
||||
# Automatically start activate-kestrel.service on boot.
|
||||
RUN ["ln", "-s", "/usr/share/dotnet/dotnet", "/usr/bin/dotnet"]
|
||||
RUN ["ln", "-s", "/usr/lib/systemd/system/activate-kestrel.service", "/etc/systemd/system/multi-user.target.wants/activate-kestrel.service"]
|
||||
|
||||
# Launch systemd.
|
||||
CMD ["/sbin/init"]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[Unit]
|
||||
Requires=activate-kestrel.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/dotnet SampleApp.dll
|
||||
WorkingDirectory=/publish
|
||||
NonBlocking=true
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[Unit]
|
||||
Description=Kestrel Activation
|
||||
|
||||
[Socket]
|
||||
ListenStream=8080
|
||||
NoDelay=true
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Ensure that dotnet is added to the PATH.
|
||||
# build.sh should always be run before this script to create the .build/ directory and restore packages.
|
||||
scriptDir=$(dirname "${BASH_SOURCE[0]}")
|
||||
repoDir=$(cd $scriptDir/../../.. && pwd)
|
||||
source ./.build/KoreBuild.sh -r $repoDir --quiet
|
||||
|
||||
dotnet publish -f netcoreapp1.1 ./samples/SampleApp/
|
||||
cp -R ./samples/SampleApp/bin/Debug/netcoreapp1.1/publish/ $scriptDir
|
||||
cp -R ~/.dotnet/ $scriptDir
|
||||
|
||||
image=$(docker build -qf $scriptDir/Dockerfile $scriptDir)
|
||||
container=$(docker run -Ptd --privileged $image)
|
||||
|
||||
# Try to connect to SampleApp once a second up to 10 times.
|
||||
for i in {1..10}; do curl $(docker port $container 8080/tcp) && exit 0 || sleep 1; done
|
||||
|
||||
exit -1
|
||||
|
|
@ -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.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
|
@ -98,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
};
|
||||
var listenerContext = new ListenerContext(serviceContext)
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000))
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
|
@ -16,21 +18,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
public class ChunkedRequestTests
|
||||
{
|
||||
public static TheoryData<TestServiceContext> ConnectionFilterData
|
||||
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
|
||||
{
|
||||
get
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
return new TheoryData<TestServiceContext>
|
||||
{
|
||||
{
|
||||
new TestServiceContext()
|
||||
},
|
||||
{
|
||||
new TestServiceContext(new PassThroughConnectionFilter())
|
||||
}
|
||||
};
|
||||
ConnectionAdapters = { new PassThroughConnectionAdapter() }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private async Task App(HttpContext httpContext)
|
||||
{
|
||||
|
|
@ -61,10 +56,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10TransferEncoding(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10TransferEncoding(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(App, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(App, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -88,10 +85,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10KeepAliveTransferEncoding(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10KeepAliveTransferEncoding(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(AppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(AppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -127,9 +126,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
|
@ -140,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -177,9 +178,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task TrailingHeadersAreParsed(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task TrailingHeadersAreParsed(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
var requestCount = 10;
|
||||
var requestsReceived = 0;
|
||||
|
||||
|
|
@ -211,7 +213,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
var response = string.Join("\r\n", new string[] {
|
||||
"HTTP/1.1 200 OK",
|
||||
|
|
@ -262,13 +264,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task TrailingHeadersCountTowardsHeadersTotalSizeLimit(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task TrailingHeadersCountTowardsHeadersTotalSizeLimit(ListenOptions listenOptions)
|
||||
{
|
||||
const string transferEncodingHeaderLine = "Transfer-Encoding: chunked";
|
||||
const string headerLine = "Header: value";
|
||||
const string trailingHeaderLine = "Trailing-Header: trailing-value";
|
||||
|
||||
var testContext = new TestServiceContext();
|
||||
testContext.ServerOptions.Limits.MaxRequestHeadersTotalSize =
|
||||
transferEncodingHeaderLine.Length + 2 +
|
||||
headerLine.Length + 2 +
|
||||
|
|
@ -278,7 +281,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
var buffer = new byte[128];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) ; // read to end
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -305,20 +308,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task TrailingHeadersCountTowardsHeaderCountLimit(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task TrailingHeadersCountTowardsHeaderCountLimit(ListenOptions listenOptions)
|
||||
{
|
||||
const string transferEncodingHeaderLine = "Transfer-Encoding: chunked";
|
||||
const string headerLine = "Header: value";
|
||||
const string trailingHeaderLine = "Trailing-Header: trailing-value";
|
||||
|
||||
var testContext = new TestServiceContext();
|
||||
testContext.ServerOptions.Limits.MaxRequestHeaderCount = 2;
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var buffer = new byte[128];
|
||||
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) ; // read to end
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -345,9 +349,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ExtensionsAreIgnored(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ExtensionsAreIgnored(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
var requestCount = 10;
|
||||
var requestsReceived = 0;
|
||||
|
||||
|
|
@ -379,7 +384,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
var response = string.Join("\r\n", new string[] {
|
||||
"HTTP/1.1 200 OK",
|
||||
|
|
@ -430,9 +435,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task InvalidLengthResultsIn400(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task InvalidLengthResultsIn400(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
|
@ -448,7 +454,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -472,9 +478,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task InvalidSizedDataResultsIn400(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task InvalidSizedDataResultsIn400(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
|
@ -490,7 +497,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -516,13 +523,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ChunkedNotFinalTransferCodingResultsIn400(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ChunkedNotFinalTransferCodingResultsIn400(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
using (var server = new TestServer(httpContext =>
|
||||
{
|
||||
return TaskCache.CompletedTask;
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
|
@ -14,32 +16,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
public class ChunkedResponseTests
|
||||
{
|
||||
public static TheoryData<TestServiceContext> ConnectionFilterData
|
||||
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
|
||||
{
|
||||
get
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
return new TheoryData<TestServiceContext>
|
||||
{
|
||||
{
|
||||
new TestServiceContext()
|
||||
},
|
||||
{
|
||||
new TestServiceContext(new PassThroughConnectionFilter())
|
||||
}
|
||||
};
|
||||
ConnectionAdapters = { new PassThroughConnectionAdapter() }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ResponsesAreChunkedAutomatically(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ResponsesAreChunkedAutomatically(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6);
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -64,14 +61,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ResponsesAreNotChunkedAutomaticallyForHttp10Requests(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ResponsesAreNotChunkedAutomaticallyForHttp10Requests(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
await httpContext.Response.WriteAsync("Hello ");
|
||||
await httpContext.Response.WriteAsync("World!");
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -91,14 +90,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ResponsesAreChunkedAutomaticallyForHttp11NonKeepAliveRequests(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
await httpContext.Response.WriteAsync("Hello ");
|
||||
await httpContext.Response.WriteAsync("World!");
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -125,15 +126,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task SettingConnectionCloseHeaderInAppDoesNotDisableChunking(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task SettingConnectionCloseHeaderInAppDoesNotDisableChunking(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
httpContext.Response.Headers["Connection"] = "close";
|
||||
await httpContext.Response.WriteAsync("Hello ");
|
||||
await httpContext.Response.WriteAsync("World!");
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -159,16 +162,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroLengthWritesAreIgnored(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroLengthWritesAreIgnored(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6);
|
||||
await response.Body.WriteAsync(new byte[0], 0, 0);
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -193,9 +198,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroLengthWritesFlushHeaders(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroLengthWritesFlushHeaders(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var flushed = new SemaphoreSlim(0, 1);
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
|
|
@ -206,7 +213,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
await flushed.WaitAsync();
|
||||
|
||||
await response.WriteAsync("Hello World!");
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -235,14 +242,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.Body.WriteAsync(new byte[0], 0, 0);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -263,15 +272,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ConnectionClosedIfExceptionThrownAfterWrite(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ConnectionClosedIfExceptionThrownAfterWrite(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World!"), 0, 12);
|
||||
throw new Exception();
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -294,15 +305,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ConnectionClosedIfExceptionThrownAfterZeroLengthWrite(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ConnectionClosedIfExceptionThrownAfterZeroLengthWrite(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
await response.Body.WriteAsync(new byte[0], 0, 0);
|
||||
throw new Exception();
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -324,9 +337,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task WritesAreFlushedPriorToResponseCompletion(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task WritesAreFlushedPriorToResponseCompletion(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var flushWh = new ManualResetEventSlim();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
|
|
@ -338,7 +353,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
flushWh.Wait();
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -368,9 +383,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ChunksCanBeWrittenManually(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ChunksCanBeWrittenManually(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
|
@ -379,7 +396,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("6\r\nHello \r\n"), 0, 11);
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("6\r\nWorld!\r\n"), 0, 11);
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("0\r\n\r\n"), 0, 5);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,26 +3,34 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class ConnectionFilterTests
|
||||
public class ConnectionAdapterTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanReadAndWriteWithRewritingConnectionFilter()
|
||||
public async Task CanReadAndWriteWithRewritingConnectionAdapter()
|
||||
{
|
||||
var filter = new RewritingConnectionFilter();
|
||||
var serviceContext = new TestServiceContext(filter);
|
||||
var adapter = new RewritingConnectionAdapter();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters = { adapter }
|
||||
};
|
||||
|
||||
var serviceContext = new TestServiceContext();
|
||||
|
||||
var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?";
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext))
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -37,15 +45,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
Assert.Equal(sendString.Length, filter.BytesRead);
|
||||
Assert.Equal(sendString.Length, adapter.BytesRead);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadAndWriteWithAsyncConnectionFilter()
|
||||
public async Task CanReadAndWriteWithAsyncConnectionAdapter()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new AsyncConnectionFilter());
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters = { new AsyncConnectionAdapter() }
|
||||
};
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext))
|
||||
var serviceContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -65,15 +78,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowingSynchronousConnectionFilterDoesNotCrashServer()
|
||||
public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new ThrowingConnectionFilter());
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters = { new ThrowingConnectionAdapter() }
|
||||
};
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext))
|
||||
var serviceContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
// Will throw because the exception in the connection filter will close the connection.
|
||||
// Will throw because the exception in the connection adapter will close the connection.
|
||||
await Assert.ThrowsAsync<IOException>(async () =>
|
||||
{
|
||||
await connection.Send(
|
||||
|
|
@ -91,42 +109,50 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
private class RewritingConnectionFilter : IConnectionFilter
|
||||
private class RewritingConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
private RewritingStream _rewritingStream;
|
||||
|
||||
public Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
_rewritingStream = new RewritingStream(context.Connection);
|
||||
context.Connection = _rewritingStream;
|
||||
return TaskCache.CompletedTask;
|
||||
_rewritingStream = new RewritingStream(context.ConnectionStream);
|
||||
return Task.FromResult<IAdaptedConnection>(new AdaptedConnection(_rewritingStream));
|
||||
}
|
||||
|
||||
public int BytesRead => _rewritingStream.BytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncConnectionFilter : IConnectionFilter
|
||||
private class AsyncConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
public async Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
var oldConnection = context.Connection;
|
||||
|
||||
// Set Connection to null to ensure it isn't used until the returned task completes.
|
||||
context.Connection = null;
|
||||
await Task.Delay(100);
|
||||
|
||||
context.Connection = new RewritingStream(oldConnection);
|
||||
return new AdaptedConnection(new RewritingStream(context.ConnectionStream));
|
||||
}
|
||||
}
|
||||
|
||||
private class ThrowingConnectionFilter : IConnectionFilter
|
||||
private class ThrowingConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
public Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
private class AdaptedConnection : IAdaptedConnection
|
||||
{
|
||||
public AdaptedConnection(Stream adaptedStream)
|
||||
{
|
||||
ConnectionStream = adaptedStream;
|
||||
}
|
||||
|
||||
public Stream ConnectionStream { get; }
|
||||
|
||||
public void PrepareRequest(IFeatureCollection requestFeatures)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class RewritingStream : Stream
|
||||
{
|
||||
private readonly Stream _innerStream;
|
||||
|
|
@ -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.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
};
|
||||
var context = new ListenerContext(serviceContext)
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://127.0.0.1:0"),
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
|
||||
Thread = engine.Threads[0]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
|
|
@ -11,14 +10,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public class CreateIPEndpointTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("localhost", "127.0.0.1")] // https://github.com/aspnet/KestrelHttpServer/issues/231
|
||||
[InlineData("10.10.10.10", "10.10.10.10")]
|
||||
[InlineData("[::1]", "::1")]
|
||||
[InlineData("randomhost", "::")] // "::" is IPAddress.IPv6Any
|
||||
[InlineData("*", "::")] // "::" is IPAddress.IPv6Any
|
||||
public void CorrectIPEndpointsAreCreated(string host, string expectedAddress)
|
||||
{
|
||||
var endpoint = UvTcpHandle.CreateIPEndpoint(ServerAddress.FromUrl($"http://{host}:5000/"));
|
||||
var endpoint = KestrelServer.CreateIPEndPoint(ServerAddress.FromUrl($"http://{host}:5000/"));
|
||||
Assert.NotNull(endpoint);
|
||||
Assert.Equal(IPAddress.Parse(expectedAddress), endpoint.Address);
|
||||
Assert.Equal(5000, endpoint.Port);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
|
@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
|
@ -26,55 +28,47 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
/// </summary>
|
||||
public class EngineTests
|
||||
{
|
||||
public static TheoryData<TestServiceContext> ConnectionFilterData
|
||||
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
|
||||
{
|
||||
get
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)),
|
||||
new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
return new TheoryData<TestServiceContext>
|
||||
{
|
||||
{
|
||||
new TestServiceContext()
|
||||
},
|
||||
{
|
||||
new TestServiceContext(new PassThroughConnectionFilter())
|
||||
}
|
||||
};
|
||||
ConnectionAdapters = { new PassThroughConnectionAdapter() }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public void EngineCanStartAndStop(TestServiceContext testContext)
|
||||
[Fact]
|
||||
public void EngineCanStartAndStop()
|
||||
{
|
||||
var engine = new KestrelEngine(testContext);
|
||||
var engine = new KestrelEngine(new TestServiceContext());
|
||||
engine.Start(1);
|
||||
engine.Dispose();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public void ListenerCanCreateAndDispose(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public void ListenerCanCreateAndDispose(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
testContext.App = TestApp.EchoApp;
|
||||
var engine = new KestrelEngine(testContext);
|
||||
engine.Start(1);
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
var started = engine.CreateServer(address);
|
||||
var started = engine.CreateServer(listenOptions);
|
||||
started.Dispose();
|
||||
engine.Dispose();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public void ConnectionCanReadAndWrite(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public void ConnectionCanReadAndWrite(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
testContext.App = TestApp.EchoApp;
|
||||
var engine = new KestrelEngine(testContext);
|
||||
engine.Start(1);
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
var started = engine.CreateServer(address);
|
||||
var started = engine.CreateServer(listenOptions);
|
||||
|
||||
var socket = TestConnection.CreateConnectedLoopbackSocket(address.Port);
|
||||
var socket = TestConnection.CreateConnectedLoopbackSocket(listenOptions.IPEndPoint.Port);
|
||||
var data = "Hello World";
|
||||
socket.Send(Encoding.ASCII.GetBytes($"POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\n{data}"));
|
||||
var buffer = new byte[data.Length];
|
||||
|
|
@ -89,10 +83,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10RequestReceivesHttp11Response(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10RequestReceivesHttp11Response(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -112,10 +108,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http11(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http11(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -143,9 +141,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task HeadersAndStreamsAreReused(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task HeadersAndStreamsAreReused(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
var streamCount = 0;
|
||||
var requestHeadersCount = 0;
|
||||
var responseHeadersCount = 0;
|
||||
|
|
@ -222,10 +221,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10ContentLength(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10ContentLength(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -245,10 +246,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10KeepAlive(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10KeepAlive(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -278,10 +281,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -311,10 +316,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Http10KeepAliveContentLength(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Http10KeepAliveContentLength(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -346,10 +353,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task Expect100ContinueForBody(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task Expect100ContinueForBody(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -376,10 +385,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task DisconnectingClient(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task DisconnectingClient(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoApp, testContext, listenOptions))
|
||||
{
|
||||
var socket = TestConnection.CreateConnectedLoopbackSocket(server.Port);
|
||||
await Task.Delay(200);
|
||||
|
|
@ -404,10 +415,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EmptyApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EmptyApp, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -434,13 +447,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroContentLengthSetAutomaticallyForNonKeepAliveRequests(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroContentLengthSetAutomaticallyForNonKeepAliveRequests(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
Assert.Equal(0, await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1).TimeoutAfter(TimeSpan.FromSeconds(10)));
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -476,10 +491,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EmptyApp, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EmptyApp, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -497,9 +514,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
|
|
@ -510,7 +529,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var statusString = await reader.ReadLineAsync();
|
||||
response.StatusCode = int.Parse(statusString);
|
||||
}
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -548,14 +567,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ZeroContentLengthAssumedOnNonKeepAliveRequestsWithoutContentLengthOrTransferEncodingHeader(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ZeroContentLengthAssumedOnNonKeepAliveRequestsWithoutContentLengthOrTransferEncodingHeader(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
// This will hang if 0 content length is not assumed by the server
|
||||
Assert.Equal(0, await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1).TimeoutAfter(TimeSpan.FromSeconds(10)));
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -594,16 +615,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ConnectionClosedAfter101Response(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ConnectionClosedAfter101Response(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async httpContext =>
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
var stream = await httpContext.Features.Get<IHttpUpgradeFeature>().UpgradeAsync();
|
||||
var response = Encoding.ASCII.GetBytes("hello, world");
|
||||
await stream.WriteAsync(response, 0, response.Length);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -637,9 +660,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ThrowingResultsIn500Response(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ThrowingResultsIn500Response(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
bool onStartingCalled = false;
|
||||
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
|
|
@ -657,7 +682,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
// Anything added to the ResponseHeaders dictionary is ignored
|
||||
response.Headers["Content-Length"] = "11";
|
||||
throw new Exception();
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -687,9 +712,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ThrowingAfterWritingKillsConnection(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ThrowingAfterWritingKillsConnection(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
bool onStartingCalled = false;
|
||||
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
|
|
@ -707,7 +734,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
throw new Exception();
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -729,9 +756,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ThrowingAfterPartialWriteKillsConnection(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ThrowingAfterPartialWriteKillsConnection(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
bool onStartingCalled = false;
|
||||
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
|
|
@ -749,7 +778,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5);
|
||||
throw new Exception();
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -770,10 +799,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Equal(1, testLogger.ApplicationErrorsLogged);
|
||||
}
|
||||
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes(ListenOptions listenOptions)
|
||||
{
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext))
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(TestApp.EchoAppChunked, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -819,9 +850,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var onStartingCallCount1 = 0;
|
||||
var onStartingCallCount2 = 0;
|
||||
var failedWriteCount = 0;
|
||||
|
|
@ -853,7 +886,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Same(onStartingException, writeException.InnerException);
|
||||
|
||||
failedWriteCount++;
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -884,9 +917,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var onCompletedCalled1 = false;
|
||||
var onCompletedCalled2 = false;
|
||||
|
||||
|
|
@ -910,7 +945,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -934,9 +969,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task RequestsCanBeAbortedMidRead(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var readTcs = new TaskCompletionSource<object>();
|
||||
var registrationTcs = new TaskCompletionSource<int>();
|
||||
var requestId = 0;
|
||||
|
|
@ -975,7 +1012,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
readTcs.SetException(new Exception("This shouldn't be reached."));
|
||||
}
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1005,9 +1042,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Equal(2, abortedRequestId);
|
||||
}
|
||||
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task FailedWritesResultInAbortedRequest(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task FailedWritesResultInAbortedRequest(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
// This should match _maxBytesPreCompleted in SocketOutput
|
||||
var maxBytesPreCompleted = 65536;
|
||||
// Ensure string is long enough to disable write-behind buffering
|
||||
|
|
@ -1044,7 +1083,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
writeTcs.SetException(new Exception("This shouldn't be reached."));
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1066,9 +1105,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var testLogger = new TestApplicationErrorLogger();
|
||||
testContext.Log = new KestrelTrace(testLogger);
|
||||
|
||||
|
|
@ -1077,7 +1118,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var response = httpContext.Response;
|
||||
response.Headers["Content-Length"] = new[] { "11" };
|
||||
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1099,14 +1140,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(httpContext =>
|
||||
{
|
||||
httpContext.Abort();
|
||||
return TaskCache.CompletedTask;
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1121,9 +1164,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task RequestHeadersAreResetOnEachRequest(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task RequestHeadersAreResetOnEachRequest(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
IHeaderDictionary originalRequestHeaders = null;
|
||||
var firstRequest = true;
|
||||
|
||||
|
|
@ -1143,7 +1188,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1168,9 +1213,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task ResponseHeadersAreResetOnEachRequest(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task ResponseHeadersAreResetOnEachRequest(ListenOptions listenOptions)
|
||||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
IHeaderDictionary originalResponseHeaders = null;
|
||||
var firstRequest = true;
|
||||
|
||||
|
|
@ -1190,7 +1237,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1243,11 +1290,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task OnStartingCallbacksAreCalledInLastInFirstOutOrder(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task OnStartingCallbacksAreCalledInLastInFirstOutOrder(ListenOptions listenOptions)
|
||||
{
|
||||
const string response = "hello, world";
|
||||
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var callOrder = new Stack<int>();
|
||||
var onStartingTcs = new TaskCompletionSource<object>();
|
||||
|
||||
|
|
@ -1267,7 +1316,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
context.Response.ContentLength = response.Length;
|
||||
await context.Response.WriteAsync(response);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1292,11 +1341,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task OnCompletedCallbacksAreCalledInLastInFirstOutOrder(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task OnCompletedCallbacksAreCalledInLastInFirstOutOrder(ListenOptions listenOptions)
|
||||
{
|
||||
const string response = "hello, world";
|
||||
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var callOrder = new Stack<int>();
|
||||
var onCompletedTcs = new TaskCompletionSource<object>();
|
||||
|
||||
|
|
@ -1316,7 +1367,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
context.Response.ContentLength = response.Length;
|
||||
await context.Response.WriteAsync(response);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -1341,11 +1392,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ConnectionFilterData))]
|
||||
public async Task UpgradeRequestIsNotKeptAliveOrChunked(TestServiceContext testContext)
|
||||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task UpgradeRequestIsNotKeptAliveOrChunked(ListenOptions listenOptions)
|
||||
{
|
||||
const string message = "Hello World";
|
||||
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
var upgradeFeature = context.Features.Get<IHttpUpgradeFeature>();
|
||||
|
|
@ -1359,7 +1412,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
await duplexStream.WriteAsync(buffer, 0, read);
|
||||
}, testContext))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
|
|
@ -28,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
};
|
||||
var listenerContext = new ListenerContext(serviceContext)
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000))
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
};
|
||||
var listenerContext = new ListenerContext(_serviceContext)
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000))
|
||||
};
|
||||
_connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
|
|
@ -440,7 +441,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public void InitializeStreamsResetsStreams()
|
||||
{
|
||||
// Arrange
|
||||
var messageBody = MessageBody.For(HttpVersion.Http11, (FrameRequestHeaders)_frame.RequestHeaders, _frame);
|
||||
var messageBody = MessageBody.For(Kestrel.Internal.Http.HttpVersion.Http11, (FrameRequestHeaders)_frame.RequestHeaders, _frame);
|
||||
_frame.InitializeStreams(messageBody);
|
||||
|
||||
var originalRequestBody = _frame.RequestBody;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
|
|
@ -13,33 +14,33 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class HttpsConnectionFilterTests
|
||||
public class HttpsConnectionAdapterTests
|
||||
{
|
||||
private static string _serverAddress = "https://127.0.0.1:0/";
|
||||
private static X509Certificate2 _x509Certificate2 = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword");
|
||||
private static X509Certificate2 _x509Certificate2 = new X509Certificate2("TestResources/testCert.pfx", "testPassword");
|
||||
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/240
|
||||
// This test currently fails on mono because of an issue with SslStream.
|
||||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task CanReadAndWriteWithHttpsConnectionFilter()
|
||||
public async Task CanReadAndWriteWithHttpsConnectionAdapter()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions { ServerCertificate = _x509Certificate2 },
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 })
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(App, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(App, serviceContext, listenOptions))
|
||||
{
|
||||
var result = await HttpClientSlim.PostAsync($"https://localhost:{server.Port}/",
|
||||
new FormUrlEncodedContent(new[] {
|
||||
|
|
@ -54,16 +55,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task RequireCertificateFailsWhenNoCertificate()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(App, serviceContext, _serverAddress))
|
||||
|
||||
using (var server = new TestServer(App, serviceContext, listenOptions))
|
||||
{
|
||||
await Assert.ThrowsAnyAsync<Exception>(
|
||||
() => HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/"));
|
||||
|
|
@ -73,21 +79,25 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task AllowCertificateContinuesWhenNoCertificate()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.AllowCertificate
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.AllowCertificate
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context =>
|
||||
{
|
||||
Assert.Equal(context.Features.Get<ITlsConnectionFeature>(), null);
|
||||
return context.Response.WriteAsync("hello world");
|
||||
},
|
||||
serviceContext, _serverAddress))
|
||||
serviceContext, listenOptions))
|
||||
{
|
||||
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
|
||||
Assert.Equal("hello world", result);
|
||||
|
|
@ -97,24 +107,24 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact]
|
||||
public void ThrowsWhenNoServerCertificateIsProvided()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions(),
|
||||
new NoOpConnectionFilter())
|
||||
Assert.Throws<ArgumentException>(() => new HttpsConnectionAdapter(
|
||||
new HttpsConnectionAdapterOptions())
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsesProvidedServerCertificate()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 })
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, listenOptions))
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
|
|
@ -132,15 +142,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task CertificatePassedToHttpContext()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context =>
|
||||
{
|
||||
|
|
@ -150,7 +164,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.NotNull(context.Connection.ClientCertificate);
|
||||
return context.Response.WriteAsync("hello world");
|
||||
},
|
||||
serviceContext, _serverAddress))
|
||||
serviceContext, listenOptions))
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
|
|
@ -167,16 +181,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task HttpsSchemePassedToRequestFeature()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(
|
||||
new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 })
|
||||
}
|
||||
};
|
||||
var serviceContext = new TestServiceContext();
|
||||
|
||||
using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), serviceContext, listenOptions))
|
||||
{
|
||||
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
|
||||
Assert.Equal("https", result);
|
||||
|
|
@ -186,17 +200,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task DoesNotSupportTls10()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), serviceContext, listenOptions))
|
||||
{
|
||||
// SslStream is used to ensure the certificate is actually passed to the server
|
||||
// HttpClient might not send the certificate because it is invalid or it doesn't match any
|
||||
|
|
@ -216,23 +234,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(ClientCertificateMode mode)
|
||||
{
|
||||
var clientCertificateValidationCalled = false;
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) =>
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
clientCertificateValidationCalled = true;
|
||||
Assert.NotNull(certificate);
|
||||
Assert.NotNull(chain);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) =>
|
||||
{
|
||||
clientCertificateValidationCalled = true;
|
||||
Assert.NotNull(certificate);
|
||||
Assert.NotNull(chain);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, listenOptions))
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
|
|
@ -249,17 +271,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData(ClientCertificateMode.RequireCertificate)]
|
||||
public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode)
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, listenOptions))
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
|
|
@ -275,16 +301,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[InlineData(ClientCertificateMode.RequireCertificate)]
|
||||
public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCertificateMode mode)
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode,
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = mode
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(context => TaskCache.CompletedTask, serviceContext, listenOptions))
|
||||
{
|
||||
using (var client = new TcpClient())
|
||||
{
|
||||
|
|
@ -298,15 +328,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
[Fact(Skip = "SslStream hanging on write after update to CoreFx 4.4 (https://github.com/dotnet/corefx/issues/14698)")]
|
||||
public async Task CertificatePassedToHttpContextIsNotDisposed()
|
||||
{
|
||||
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
|
||||
new HttpsConnectionFilterOptions
|
||||
var serviceContext = new TestServiceContext();
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
ConnectionAdapters =
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
},
|
||||
new NoOpConnectionFilter())
|
||||
);
|
||||
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
RequestDelegate app = context =>
|
||||
{
|
||||
|
|
@ -318,7 +352,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
return context.Response.WriteAsync("hello world");
|
||||
};
|
||||
|
||||
using (var server = new TestServer(app, serviceContext, _serverAddress))
|
||||
using (var server = new TestServer(app, serviceContext, listenOptions))
|
||||
{
|
||||
// SslStream is used to ensure the certificate is actually passed to the server
|
||||
// HttpClient might not send the certificate because it is invalid or it doesn't match any
|
||||
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class KestrelServerInformationTests
|
||||
public class KestrelServerOptionsTests
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
[Fact]
|
||||
|
|
@ -39,6 +40,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
#pragma warning restore CS0612
|
||||
|
||||
[Fact]
|
||||
public void NoDelayDefaultsToTrue()
|
||||
{
|
||||
var o1 = new KestrelServerOptions();
|
||||
o1.Listen(IPAddress.Loopback, 0);
|
||||
o1.Listen(IPAddress.Loopback, 0, d =>
|
||||
{
|
||||
d.NoDelay = false;
|
||||
});
|
||||
|
||||
Assert.True(o1.ListenOptions[0].NoDelay);
|
||||
Assert.False(o1.ListenOptions[1].NoDelay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetThreadCountUsingProcessorCount()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -55,33 +56,35 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
using (var kestrelEngine = new KestrelEngine(libuv, serviceContextPrimary))
|
||||
{
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
||||
|
||||
var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
|
||||
var pipeMessage = Guid.NewGuid().ToByteArray();
|
||||
|
||||
// Start primary listener
|
||||
var kestrelThreadPrimary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadPrimary.StartAsync();
|
||||
var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary);
|
||||
var listenerPrimary = new ListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, kestrelThreadPrimary);
|
||||
var address = listenOptions.ToString();
|
||||
|
||||
// Until a secondary listener is added, TCP connections get dispatched directly
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
// Add secondary listener
|
||||
var kestrelThreadSecondary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadSecondary.StartAsync();
|
||||
var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, pipeMessage, address, kestrelThreadSecondary);
|
||||
var listenerSecondary = new ListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, kestrelThreadSecondary);
|
||||
|
||||
// Once a secondary listener is added, TCP connections start getting dispatched to it
|
||||
await AssertResponseEventually(address.ToString(), "Secondary", allowed: new[] { "Primary" });
|
||||
await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" });
|
||||
|
||||
// TCP connections will still get round-robined to the primary listener
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
await listenerSecondary.DisposeAsync();
|
||||
await kestrelThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));
|
||||
|
|
@ -129,25 +132,26 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
using (var kestrelEngine = new KestrelEngine(libuv, serviceContextPrimary))
|
||||
{
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
||||
var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
|
||||
var pipeMessage = Guid.NewGuid().ToByteArray();
|
||||
|
||||
// Start primary listener
|
||||
var kestrelThreadPrimary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadPrimary.StartAsync();
|
||||
var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary);
|
||||
var listenerPrimary = new ListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, kestrelThreadPrimary);
|
||||
var address = listenOptions.ToString();
|
||||
|
||||
// Add secondary listener
|
||||
var kestrelThreadSecondary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadSecondary.StartAsync();
|
||||
var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, pipeMessage, address, kestrelThreadSecondary);
|
||||
var listenerSecondary = new ListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, pipeMessage, listenOptions, kestrelThreadSecondary);
|
||||
|
||||
// TCP Connections get round-robined
|
||||
await AssertResponseEventually(address.ToString(), "Secondary", allowed: new[] { "Primary" });
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
await AssertResponseEventually(address, "Secondary", allowed: new[] { "Primary" });
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
// Create a pipe connection and keep it open without sending any data
|
||||
var connectTcs = new TaskCompletionSource<object>();
|
||||
|
|
@ -183,9 +187,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
await connectTcs.Task;
|
||||
|
||||
// TCP connections will still get round-robined between only the two listeners
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
await kestrelThreadPrimary.PostAsync(_ => pipe.Dispose(), null);
|
||||
|
||||
|
|
@ -196,9 +200,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
// Same for after the non-listener pipe connection is closed
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
await listenerSecondary.DisposeAsync();
|
||||
await kestrelThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));
|
||||
|
|
@ -250,21 +254,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
using (var kestrelEngine = new KestrelEngine(libuv, serviceContextPrimary))
|
||||
{
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
||||
var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
|
||||
var pipeMessage = Guid.NewGuid().ToByteArray();
|
||||
|
||||
// Start primary listener
|
||||
var kestrelThreadPrimary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadPrimary.StartAsync();
|
||||
var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary);
|
||||
var listenerPrimary = new ListenerPrimary(serviceContextPrimary);
|
||||
await listenerPrimary.StartAsync(pipeName, pipeMessage, listenOptions, kestrelThreadPrimary);
|
||||
var address = listenOptions.ToString();
|
||||
|
||||
// Add secondary listener with wrong pipe message
|
||||
var kestrelThreadSecondary = new KestrelThread(kestrelEngine);
|
||||
await kestrelThreadSecondary.StartAsync();
|
||||
var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), address, kestrelThreadSecondary);
|
||||
var listenerSecondary = new ListenerSecondary(serviceContextSecondary);
|
||||
await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), listenOptions, kestrelThreadSecondary);
|
||||
|
||||
// Wait up to 10 seconds for error to be logged
|
||||
for (var i = 0; i < 10 && primaryTrace.Logger.TotalErrorsLogged == 0; i++)
|
||||
|
|
@ -273,9 +278,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
|
||||
// TCP Connections don't get round-robined
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address));
|
||||
|
||||
await listenerSecondary.DisposeAsync();
|
||||
await kestrelThreadSecondary.StopAsync(TimeSpan.FromSeconds(1));
|
||||
|
|
|
|||
|
|
@ -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.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
|
@ -163,8 +164,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
var serverListenTcp = new UvTcpHandle(_logger);
|
||||
serverListenTcp.Init(loop, (a, b) => { });
|
||||
var address = ServerAddress.FromUrl($"http://127.0.0.1:0/");
|
||||
serverListenTcp.Bind(address);
|
||||
var endPoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
serverListenTcp.Bind(endPoint);
|
||||
var port = serverListenTcp.GetSockIPEndPoint().Port;
|
||||
serverListenTcp.Listen(128, (handle, status, error, state) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
loop.Init(_uv);
|
||||
var tcp = new UvTcpHandle(_logger);
|
||||
tcp.Init(loop, (a, b) => { });
|
||||
var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
|
||||
tcp.Bind(address);
|
||||
var endPoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
tcp.Bind(endPoint);
|
||||
tcp.Dispose();
|
||||
loop.Run();
|
||||
loop.Dispose();
|
||||
|
|
@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
loop.Init(_uv);
|
||||
var tcp = new UvTcpHandle(_logger);
|
||||
tcp.Init(loop, (a, b) => { });
|
||||
var address = ServerAddress.FromUrl($"http://127.0.0.1:0/");
|
||||
tcp.Bind(address);
|
||||
var endPoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
tcp.Bind(endPoint);
|
||||
var port = tcp.GetSockIPEndPoint().Port;
|
||||
tcp.Listen(10, (stream, status, error, state) =>
|
||||
{
|
||||
|
|
@ -107,8 +107,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
loop.Init(_uv);
|
||||
var tcp = new UvTcpHandle(_logger);
|
||||
tcp.Init(loop, (a, b) => { });
|
||||
var address = ServerAddress.FromUrl($"http://127.0.0.1:0/");
|
||||
tcp.Bind(address);
|
||||
var endPoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
tcp.Bind(endPoint);
|
||||
var port = tcp.GetSockIPEndPoint().Port;
|
||||
tcp.Listen(10, (_, status, error, state) =>
|
||||
{
|
||||
|
|
@ -157,8 +157,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
loop.Init(_uv);
|
||||
var tcp = new UvTcpHandle(_logger);
|
||||
tcp.Init(loop, (a, b) => { });
|
||||
var address = ServerAddress.FromUrl($"http://127.0.0.1:0/");
|
||||
tcp.Bind(address);
|
||||
var endPoint = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
tcp.Bind(endPoint);
|
||||
var port = tcp.GetSockIPEndPoint().Port;
|
||||
tcp.Listen(10, (_, status, error, state) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// 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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -16,6 +18,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
var testContext = new TestServiceContext();
|
||||
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
PathBase = "/\u0041\u030A"
|
||||
};
|
||||
|
||||
using (var server = new TestServer(async context =>
|
||||
{
|
||||
Assert.Equal("/\u0041\u030A", context.Request.PathBase.Value);
|
||||
|
|
@ -23,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
context.Response.Headers["Content-Length"] = new[] { "11" };
|
||||
await context.Response.WriteAsync("Hello World");
|
||||
}, testContext, "http://127.0.0.1:0/\u0041\u030A"))
|
||||
}, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||
{
|
||||
public class PassThroughConnectionAdapter : IConnectionAdapter
|
||||
{
|
||||
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
|
||||
{
|
||||
var adapted = new AdaptedConnection(new LoggingStream(context.ConnectionStream, new TestApplicationErrorLogger()));
|
||||
return Task.FromResult<IAdaptedConnection>(adapted);
|
||||
}
|
||||
|
||||
private class AdaptedConnection : IAdaptedConnection
|
||||
{
|
||||
public AdaptedConnection(Stream stream)
|
||||
{
|
||||
ConnectionStream = stream;
|
||||
}
|
||||
|
||||
public Stream ConnectionStream { get; }
|
||||
|
||||
public void PrepareRequest(IFeatureCollection requestFeatures)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// 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.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||
{
|
||||
|
||||
public class PassThroughConnectionFilter : IConnectionFilter
|
||||
{
|
||||
public Task OnConnectionAsync(ConnectionFilterContext context)
|
||||
{
|
||||
context.Connection = new LoggingStream(context.Connection, new TestApplicationErrorLogger());
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,13 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
};
|
||||
var listenerContext = new ListenerContext(serviceContext)
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000))
|
||||
};
|
||||
var connectionContext = new ConnectionContext(listenerContext)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
|
|
@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
RequestAbortedSource = new CancellationTokenSource();
|
||||
ListenerContext = new ListenerContext(new ServiceContext {ServerOptions = options})
|
||||
{
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000))
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
{
|
||||
private KestrelEngine _engine;
|
||||
private IDisposable _server;
|
||||
private ServerAddress _address;
|
||||
private ListenOptions _listenOptions;
|
||||
|
||||
public TestServer(RequestDelegate app)
|
||||
: this(app, new TestServiceContext())
|
||||
|
|
@ -24,18 +25,24 @@ namespace Microsoft.AspNetCore.Testing
|
|||
}
|
||||
|
||||
public TestServer(RequestDelegate app, TestServiceContext context)
|
||||
: this(app, context, "http://127.0.0.1:0/")
|
||||
: this(app, context, httpContextFactory: null)
|
||||
{
|
||||
}
|
||||
|
||||
public TestServer(RequestDelegate app, TestServiceContext context, string serverAddress)
|
||||
: this(app, context, serverAddress, null)
|
||||
public TestServer(RequestDelegate app, TestServiceContext context, IHttpContextFactory httpContextFactory)
|
||||
: this(app, context, new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), httpContextFactory)
|
||||
{
|
||||
}
|
||||
|
||||
public TestServer(RequestDelegate app, TestServiceContext context, string serverAddress, IHttpContextFactory httpContextFactory)
|
||||
public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions)
|
||||
: this(app, context, listenOptions, null)
|
||||
{
|
||||
}
|
||||
|
||||
public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, IHttpContextFactory httpContextFactory)
|
||||
{
|
||||
Context = context;
|
||||
_listenOptions = listenOptions;
|
||||
|
||||
context.FrameFactory = connectionContext =>
|
||||
{
|
||||
|
|
@ -46,8 +53,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
{
|
||||
_engine = new KestrelEngine(context);
|
||||
_engine.Start(1);
|
||||
_address = ServerAddress.FromUrl(serverAddress);
|
||||
_server = _engine.CreateServer(_address);
|
||||
_server = _engine.CreateServer(_listenOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -57,7 +63,7 @@ namespace Microsoft.AspNetCore.Testing
|
|||
}
|
||||
}
|
||||
|
||||
public int Port => _address.Port;
|
||||
public int Port => _listenOptions.IPEndPoint.Port;
|
||||
|
||||
public TestServiceContext Context { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
|
|
@ -26,12 +26,6 @@ namespace Microsoft.AspNetCore.Testing
|
|||
ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
public TestServiceContext(IConnectionFilter filter)
|
||||
: this()
|
||||
{
|
||||
ServerOptions.ConnectionFilter = filter;
|
||||
}
|
||||
|
||||
public string DateHeaderValue { get; }
|
||||
|
||||
public RequestDelegate App
|
||||
|
|
|
|||
Loading…
Reference in New Issue