Update addresses in IServerAddressesFeature with assigned dynamic ports (#758)

- Change most tests to use dynamic ports, rather than a fixed port obtained from GetNextPort().
- Add several new cases to `AddressRegistrationTests` and `ServerAddressFacts`.
- Remove project `test\Microsoft.AspNetCore.Server.Kestrel.TestCommon`.  It's not longer needed, since only `AddressRegistrationTests` uses `GetNextPort()`.
This commit is contained in:
Mike Harder 2016-05-04 11:27:23 -07:00
parent f8c2c2470f
commit 14bbba0641
24 changed files with 316 additions and 211 deletions

View File

@ -34,8 +34,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.Kestrel.FunctionalTests", "test\Microsoft.AspNetCore.Server.Kestrel.FunctionalTests\Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.xproj", "{9559A5F1-080C-4909-B6CF-7E4B3DC55748}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Server.Kestrel.TestCommon", "test\Microsoft.AspNetCore.Server.Kestrel.TestCommon\Microsoft.AspNetCore.Server.Kestrel.TestCommon.xproj", "{A485B4F9-0392-478C-B19A-F4DE6B17F491}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -74,10 +72,6 @@ Global
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Release|Any CPU.Build.0 = Release|Any CPU
{A485B4F9-0392-478C-B19A-F4DE6B17F491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A485B4F9-0392-478C-B19A-F4DE6B17F491}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A485B4F9-0392-478C-B19A-F4DE6B17F491}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A485B4F9-0392-478C-B19A-F4DE6B17F491}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -91,6 +85,5 @@ Global
{8CBA6FE3-3CC9-4420-8AA3-123E983734C2} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD}
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
{9559A5F1-080C-4909-B6CF-7E4B3DC55748} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{A485B4F9-0392-478C-B19A-F4DE6B17F491} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
EndGlobalSection
EndGlobal

View File

@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
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;
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
return socket;
}

View File

@ -27,6 +27,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
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;
socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this);
return socket;
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
@ -96,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
engine.Start(threadCount);
var atLeastOneListener = false;
foreach (var address in _serverAddresses.Addresses)
foreach (var address in _serverAddresses.Addresses.ToArray())
{
var parsedAddress = ServerAddress.FromUrl(address);
if (parsedAddress == null)
@ -108,6 +109,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel
atLeastOneListener = true;
_disposables.Push(engine.CreateServer(
parsedAddress));
// If requested port was "0", replace with assigned dynamic port.
_serverAddresses.Addresses.Remove(address);
_serverAddresses.Addresses.Add(parsedAddress.ToString());
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
{
public string Host { get; private set; }
public string PathBase { get; private set; }
public int Port { get; private set; }
public int Port { get; internal set; }
public string Scheme { get; private set; }
public bool IsUnixPipe
@ -36,7 +36,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel
public override string ToString()
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase.ToLowerInvariant();
if (IsUnixPipe)
{
if (String.IsNullOrEmpty(PathBase))
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant();
}
else
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + PathBase.ToLowerInvariant();
}
}
else
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase.ToLowerInvariant();
}
}
public override int GetHashCode()

View File

@ -1,11 +1,16 @@
// 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.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Testing.xunit;
@ -16,20 +21,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class AddressRegistrationTests
{
[Theory, MemberData(nameof(AddressRegistrationDataIPv4))]
public async Task RegisterAddresses_IPv4_Success(string addressInput, string[] testUrls)
public async Task RegisterAddresses_IPv4_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
{
await RegisterAddresses_Success(addressInput, testUrls);
}
[ConditionalTheory, MemberData(nameof(AddressRegistrationDataIPv4Port80))]
[Port80SupportedCondition]
public async Task RegisterAddresses_IPv4Port80_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
{
await RegisterAddresses_Success(addressInput, testUrls);
}
[ConditionalTheory, MemberData(nameof(AddressRegistrationDataIPv6))]
[IPv6SupportedCondition]
public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] testUrls)
public async Task RegisterAddresses_IPv6_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
{
await RegisterAddresses_Success(addressInput, testUrls);
}
public async Task RegisterAddresses_Success(string addressInput, string[] testUrls)
[ConditionalTheory, MemberData(nameof(AddressRegistrationDataIPv6ScopeId))]
[OSSkipCondition(OperatingSystems.Linux, SkipReason = "HttpClient does not support IPv6 with scope ID on Linux (https://github.com/dotnet/corefx/issues/8235).")]
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "HttpClient does not support IPv6 with scope ID on Mac (https://github.com/dotnet/corefx/issues/8235).")]
public async Task RegisterAddresses_IPv6ScopeId_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
{
await RegisterAddresses_Success(addressInput, testUrls);
}
public async Task RegisterAddresses_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
{
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
@ -49,49 +70,129 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient())
{
foreach (var testUrl in testUrls)
foreach (var testUrl in testUrls(host.ServerFeatures.Get<IServerAddressesFeature>()))
{
var responseText = await client.GetStringAsync(testUrl);
Assert.Equal(testUrl, responseText);
var response = await client.GetAsync(testUrl);
// Compare the response with the RequestMessage.RequestUri, rather than testUrl directly.
// Required to handle IPv6 addresses with zone index, like "fe80::3%1"
Assert.Equal(
response.RequestMessage.RequestUri.ToString(),
await response.Content.ReadAsStringAsync());
}
}
}
}
public static TheoryData<string, string[]> AddressRegistrationDataIPv4
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv4
{
get
{
var port1 = PortManager.GetPort();
var port2 = PortManager.GetPort();
var dataset = new TheoryData<string, string[]>();
dataset.Add($"{port1}", new[] { $"http://localhost:{port1}/" });
dataset.Add($"{port1};{port2}", new[] { $"http://localhost:{port1}/", $"http://localhost:{port2}/" });
dataset.Add($"http://127.0.0.1:{port1}/", new[] { $"http://127.0.0.1:{port1}/", });
dataset.Add($"http://localhost:{port1}/base/path", new[] { $"http://localhost:{port1}/base/path" });
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
// Default host and port
dataset.Add(null, _ => new[] { "http://localhost:5000/" });
dataset.Add(string.Empty, _ => new[] { "http://localhost:5000/" });
// Static port
var port1 = GetNextPort();
var port2 = GetNextPort();
dataset.Add($"{port1}", _ => new[] { $"http://localhost:{port1}/" });
dataset.Add($"{port1};{port2}", _ => new[] { $"http://localhost:{port1}/", $"http://localhost:{port2}/" });
// Ensure "localhost" and "127.0.0.1" are equivalent
dataset.Add($"http://localhost:{port1}", _ => new[] { $"http://localhost:{port1}/", $"http://127.0.0.1:{port1}/" });
dataset.Add($"http://127.0.0.1:{port1}", _ => new[] { $"http://localhost:{port1}/", $"http://127.0.0.1:{port1}/" });
// Path after port
dataset.Add($"http://localhost:{port1}/base/path", _ => new[] { $"http://localhost:{port1}/base/path" });
// Dynamic port
dataset.Add("0", GetTestUrls);
dataset.Add("http://localhost:0/", GetTestUrls);
dataset.Add($"http://{Dns.GetHostName()}:0/", GetTestUrls);
var ipv4Addresses = Dns.GetHostAddressesAsync(Dns.GetHostName()).Result
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);
foreach (var ip in ipv4Addresses)
{
dataset.Add($"http://{ip}:0/", GetTestUrls);
}
return dataset;
}
}
public static TheoryData<string, string[]> AddressRegistrationDataIPv6
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv4Port80
{
get
{
var port = PortManager.GetPort();
var dataset = new TheoryData<string, string[]>();
dataset.Add($"http://*:{port}/", new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
dataset.Add($"http://localhost:{port}/", new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/",
/* // https://github.com/aspnet/KestrelHttpServer/issues/231
$"http://[::1]:{port}/"
*/ });
dataset.Add($"http://[::1]:{port}/", new[] { $"http://[::1]:{port}/", });
dataset.Add($"http://127.0.0.1:{port}/;http://[::1]:{port}/", new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
// Default port for HTTP (80)
dataset.Add("http://*", _ => new[] { "http://localhost/" });
dataset.Add("http://localhost", _ => new[] { "http://localhost/" });
return dataset;
}
}
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv6
{
get
{
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
// Static port
var port = GetNextPort();
dataset.Add($"http://*:{port}/", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
dataset.Add($"http://localhost:{port}/", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/",
/* // https://github.com/aspnet/KestrelHttpServer/issues/231
$"http://[::1]:{port}/"
*/ });
dataset.Add($"http://[::1]:{port}/", _ => new[] { $"http://[::1]:{port}/", });
dataset.Add($"http://127.0.0.1:{port}/;http://[::1]:{port}/", _ => new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
// Dynamic port
var ipv6Addresses = Dns.GetHostAddressesAsync(Dns.GetHostName()).Result
.Where(ip => ip.AddressFamily == AddressFamily.InterNetworkV6)
.Where(ip => ip.ScopeId == 0);
foreach (var ip in ipv6Addresses)
{
dataset.Add($"http://[{ip}]:0/", GetTestUrls);
}
return dataset;
}
}
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv6ScopeId
{
get
{
var dataset = new TheoryData<string, Func<IServerAddressesFeature, string[]>>();
// Dynamic port
var ipv6Addresses = Dns.GetHostAddressesAsync(Dns.GetHostName()).Result
.Where(ip => ip.AddressFamily == AddressFamily.InterNetworkV6)
.Where(ip => ip.ScopeId != 0);
foreach (var ip in ipv6Addresses)
{
dataset.Add($"http://[{ip}]:0/", GetTestUrls);
}
return dataset;
}
}
private static string[] GetTestUrls(IServerAddressesFeature addressesFeature)
{
return addressesFeature.Addresses
.Select(a => a.Replace("://+", "://localhost"))
.Select(a => a.EndsWith("/") ? a : a + "/")
.ToArray();
}
private void ConfigureEchoAddress(IApplicationBuilder app)
{
app.Run(context =>
@ -99,5 +200,72 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return context.Response.WriteAsync(context.Request.GetDisplayUrl());
});
}
private static int _nextPort = 8001;
private static object _portLock = new object();
private static int GetNextPort()
{
lock (_portLock)
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
while (true)
{
try
{
var port = _nextPort++;
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
return port;
}
catch (SocketException)
{
// Retry unless exhausted
if (_nextPort == 65536)
{
throw;
}
}
}
}
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
private class Port80SupportedConditionAttribute : Attribute, ITestCondition
{
private static readonly Lazy<bool> _port80Supported = new Lazy<bool>(CanBindToPort80);
public bool IsMet
{
get
{
return _port80Supported.Value;
}
}
public string SkipReason
{
get
{
return "Cannot bind to port 80 on the host.";
}
}
private static bool CanBindToPort80()
{
try
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(new IPEndPoint(IPAddress.Loopback, 80));
return true;
}
}
catch (SocketException)
{
return false;
}
}
}
}
}

View File

@ -0,0 +1,25 @@
// 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.Linq;
using Microsoft.AspNetCore.Hosting.Server.Features;
namespace Microsoft.AspNetCore.Hosting
{
public static class IWebHostPortExtensions
{
public static int GetPort(this IWebHost host)
{
return host.GetPorts().First();
}
public static IEnumerable<int> GetPorts(this IWebHost host)
{
return host.ServerFeatures.Get<IServerAddressesFeature>().Addresses
.Select(a => a.Replace("://+", "://localhost"))
.Select(a => (new Uri(a)).Port);
}
}
}

View File

@ -68,10 +68,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private async Task TestPathBase(string registerPathBase, string requestPath, string expectedPathBase, string expectedPath)
{
var port = PortManager.GetPort();
var config = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string, string> {
{ "server.urls", $"http://localhost:{port}{registerPathBase}" }
{ "server.urls", $"http://localhost:0{registerPathBase}" }
}).Build();
var builder = new WebHostBuilder()
@ -95,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://localhost:{port}{requestPath}");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}{requestPath}");
response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync();

View File

@ -1,15 +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;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public static class PortManager
{
public static int GetPort()
{
return TestCommon.PortManager.GetNextPort();
}
}
}

View File

@ -22,10 +22,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task LargeUpload()
{
var port = PortManager.GetPort();
var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls($"http://localhost:{port}/")
.UseUrls($"http://localhost:0/")
.Configure(app =>
{
app.Run(async context =>
@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
bytes[i] = (byte)i;
}
var response = await client.PostAsync($"http://localhost:{port}/", new ByteArrayContent(bytes));
var response = await client.PostAsync($"http://localhost:{host.GetPort()}/", new ByteArrayContent(bytes));
response.EnsureSuccessStatusCode();
var sizeString = await response.Content.ReadAsStringAsync();
Assert.Equal(sizeString, bytes.Length.ToString(CultureInfo.InvariantCulture));
@ -72,10 +71,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Fails on Mono on Mac because it is not 64-bit.")]
public async Task LargeMultipartUpload()
{
var port = PortManager.GetPort();
var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls($"http://localhost:{port}/")
.UseUrls($"http://localhost:0/")
.Configure(app =>
{
app.Run(async context =>
@ -111,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
var length = form.Headers.ContentLength.Value;
var response = await client.PostAsync($"http://localhost:{port}/", form);
var response = await client.PostAsync($"http://localhost:{host.GetPort()}/", form);
response.EnsureSuccessStatusCode();
Assert.Equal(length.ToString(CultureInfo.InvariantCulture), await response.Content.ReadAsStringAsync());
}
@ -137,10 +135,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task DoesNotHangOnConnectionCloseRequest()
{
var port = PortManager.GetPort();
var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls($"http://localhost:{port}")
.UseUrls($"http://localhost:0")
.Configure(app =>
{
app.Run(async context =>
@ -158,7 +155,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.Connection.Add("close");
var response = await client.GetAsync($"http://localhost:{port}/");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
}
}
@ -166,10 +163,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public void RequestPathIsNormalized()
{
var port = PortManager.GetPort();
var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls($"http://localhost:{port}/\u0041\u030A")
.UseUrls($"http://localhost:0/\u0041\u030A")
.Configure(app =>
{
app.Run(async context =>
@ -185,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
host.Start();
using (var socket = TestConnection.CreateConnectedLoopbackSocket(port))
using (var socket = TestConnection.CreateConnectedLoopbackSocket(host.GetPort()))
{
socket.Send(Encoding.ASCII.GetBytes("GET /%41%CC%8A/A/../B/%41%CC%8A HTTP/1.1\r\n\r\n"));
socket.Shutdown(SocketShutdown.Send);
@ -210,10 +206,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress)
{
var port = PortManager.GetPort();
var builder = new WebHostBuilder()
.UseKestrel()
.UseUrls($"http://{registerAddress}:{port}")
.UseUrls($"http://{registerAddress}:0")
.Configure(app =>
{
app.Run(async context =>
@ -234,7 +229,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
host.Start();
var response = await client.GetAsync($"http://{requestAddress}:{port}/");
var response = await client.GetAsync($"http://{requestAddress}:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var connectionFacts = await response.Content.ReadAsStringAsync();

View File

@ -21,11 +21,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task LargeDownload()
{
var port = PortManager.GetPort();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "server.urls", $"http://localhost:{port}/" }
{ "server.urls", $"http://localhost:0/" }
})
.Build();
@ -57,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://localhost:{port}/");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStreamAsync();
@ -81,11 +80,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Theory, MemberData(nameof(NullHeaderData))]
public async Task IgnoreNullHeaderValues(string headerName, StringValues headerValue, string expectedValue)
{
var port = PortManager.GetPort();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "server.urls", $"http://localhost:{port}/" }
{ "server.urls", $"http://localhost:0/" }
})
.Build();
@ -108,7 +106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://localhost:{port}/");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/");
response.EnsureSuccessStatusCode();
var headers = response.Headers;
@ -129,11 +127,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact]
public async Task OnCompleteCalledEvenWhenOnStartingNotCalled()
{
var port = PortManager.GetPort();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "server.urls", $"http://localhost:{port}/" }
{ "server.urls", $"http://localhost:0/" }
})
.Build();
@ -161,7 +158,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://localhost:{port}/");
var response = await client.GetAsync($"http://localhost:{host.GetPort()}/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.False(onStartingCalled);

View File

@ -16,11 +16,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public async Task ZeroToTenThreads(int threadCount)
{
var port = PortManager.GetPort();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "server.urls", $"http://localhost:{port}/" }
{ "server.urls", $"http://localhost:0/" }
})
.Build();
@ -48,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var requestTasks = new List<Task<string>>();
for (int i = 0; i < 20; i++)
{
var requestTask = client.GetStringAsync($"http://localhost:{port}/");
var requestTask = client.GetStringAsync($"http://localhost:{host.GetPort()}/");
requestTasks.Add(requestTask);
}

View File

@ -5,7 +5,7 @@
"Microsoft.NETCore.Platforms": "1.0.1-*",
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.TestCommon": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.0-*",
"Microsoft.AspNetCore.Testing": "1.0.0-*",
"Newtonsoft.Json": "8.0.3",
"xunit": "2.1.0"
@ -18,6 +18,7 @@
"type": "platform"
},
"System.Net.Http": "4.0.1-*",
"System.Net.Http.WinHttpHandler": "4.0.0-*",
"System.Runtime.Serialization.Primitives": "4.1.1-*"
},
"imports": [

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>a485b4f9-0392-478c-b19a-f4de6b17f491</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -1,38 +0,0 @@
using System.Net;
using System.Net.Sockets;
namespace Microsoft.AspNetCore.Server.Kestrel.TestCommon
{
public static class PortManager
{
public static int _nextPort = 8001;
public static object _portLock = new object();
public static int GetNextPort()
{
lock (_portLock)
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
while (true)
{
try
{
var port = _nextPort++;
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
return port;
}
catch (SocketException)
{
// Retry unless exhausted
if (_nextPort == 65536)
{
throw;
}
}
}
}
}
}
}
}

View File

@ -1,18 +0,0 @@
{
"version": "1.0.0-*",
"frameworks": {
"net451": {},
"netstandard1.3": {
"dependencies": {
"System.Threading": "4.0.11-*",
"System.Net.Sockets": "4.1.0-*"
},
"imports": [
"portable-net45+win8"
]
}
},
"buildOptions": {
"keyFile": "../../tools/Key.snk"
}
}

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
FrameFactory = connectionContext => new Frame<HttpContext>(
new DummyApplication(httpContext => TaskUtilities.CompletedTask), connectionContext),
Memory = memory,
ServerAddress = ServerAddress.FromUrl($"http://localhost:{TestServer.GetNextPort()}"),
ServerAddress = ServerAddress.FromUrl($"http://localhost:0"),
Thread = engine.Threads[0]
};
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);

View File

@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
testContext.App = App;
var engine = new KestrelEngine(testContext);
engine.Start(1);
var address = ServerAddress.FromUrl($"http://localhost:{TestServer.GetNextPort()}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
var started = engine.CreateServer(address);
started.Dispose();
engine.Dispose();
@ -98,14 +98,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[MemberData(nameof(ConnectionFilterData))]
public void ConnectionCanReadAndWrite(TestServiceContext testContext)
{
var port = TestServer.GetNextPort();
testContext.App = App;
var engine = new KestrelEngine(testContext);
engine.Start(1);
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
var started = engine.CreateServer(address);
var socket = TestConnection.CreateConnectedLoopbackSocket(port);
var socket = TestConnection.CreateConnectedLoopbackSocket(address.Port);
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World"));
socket.Shutdown(SocketShutdown.Send);
var buffer = new byte[8192];

View File

@ -69,18 +69,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var handler = new WinHttpHandler();
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
{ ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword") },
new NoOpConnectionFilter())
);
using (var server = new TestServer(App, serviceContext, serverAddress))
using (var server = new TestServer(App, serviceContext, "https://localhost:0/"))
{
using (var client = new HttpClient(handler))
{
var result = await client.PostAsync(serverAddress, new FormUrlEncodedContent(new[] {
var result = await client.PostAsync($"https://localhost:{server.Port}/", new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("content", "Hello World?")
}));
@ -114,7 +113,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
{
@ -124,12 +122,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
new NoOpConnectionFilter())
);
using (var server = new TestServer(App, serviceContext, serverAddress))
using (var server = new TestServer(App, serviceContext, "https://localhost:0/"))
{
using (var client = new HttpClient(handler))
{
await Assert.ThrowsAnyAsync<Exception>(
() => client.GetAsync(serverAddress));
() => client.GetAsync($"https://localhost:{server.Port}/"));
}
}
}
@ -159,7 +157,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
{
@ -175,11 +172,11 @@ 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, "https://localhost:0/"))
{
using (var client = new HttpClient(handler))
{
var result = await client.GetAsync(serverAddress);
var result = await client.GetAsync($"https://localhost:{server.Port}/");
Assert.Equal("hello world", await result.Content.ReadAsStringAsync());
}
@ -206,7 +203,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
{
@ -226,7 +222,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, "https://localhost:0/"))
{
// 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
@ -274,7 +270,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(
new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
@ -286,11 +281,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
RequestDelegate app = context => context.Response.WriteAsync(context.Request.Scheme);
using (var server = new TestServer(app, serviceContext, serverAddress))
using (var server = new TestServer(app, serviceContext, "https://localhost:0/"))
{
using (var client = new HttpClient(handler))
{
var result = await client.GetAsync(serverAddress);
var result = await client.GetAsync($"https://localhost:{server.Port}/");
Assert.Equal("https", await result.Content.ReadAsStringAsync());
}
@ -317,7 +312,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
#endif
var serverAddress = $"https://localhost:{TestServer.GetNextPort()}/";
var serviceContext = new TestServiceContext(new HttpsConnectionFilter(
new HttpsConnectionFilterOptions
{
@ -333,7 +327,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, "https://localhost:0/"))
{
// 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

View File

@ -128,7 +128,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var pipeName = @"\\.\pipe\ServerPipeDispatchConnections" + Guid.NewGuid().ToString("n");
var port = TestServer.GetNextPort();
var loop = new UvLoopHandle(_logger);
loop.Init(_uv);
@ -158,8 +157,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var serverListenTcp = new UvTcpHandle(_logger);
serverListenTcp.Init(loop, (a, b) => { });
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
serverListenTcp.Bind(address);
var port = serverListenTcp.GetSockIPEndPoint().Port;
serverListenTcp.Listen(128, (_1, status, error, _2) =>
{
var serverConnectionTcp = new UvTcpHandle(_logger);

View File

@ -76,9 +76,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop, (a, b) => { });
var port = TestServer.GetNextPort();
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
tcp.Bind(address);
var port = tcp.GetSockIPEndPoint().Port;
tcp.Listen(10, (stream, status, error, state) =>
{
var tcp2 = new UvTcpHandle(_logger);
@ -105,9 +105,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop, (a, b) => { });
var port = TestServer.GetNextPort();
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
tcp.Bind(address);
var port = tcp.GetSockIPEndPoint().Port;
tcp.Listen(10, (_, status, error, state) =>
{
var tcp2 = new UvTcpHandle(_logger);
@ -155,9 +155,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop, (a, b) => { });
var port = TestServer.GetNextPort();
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
var address = ServerAddress.FromUrl($"http://localhost:0/");
tcp.Bind(address);
var port = tcp.GetSockIPEndPoint().Port;
tcp.Listen(10, (_, status, error, state) =>
{
var tcp2 = new UvTcpHandle(_logger);

View File

@ -19,27 +19,28 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
[Theory]
[InlineData("://emptyscheme", "", "emptyscheme", 0, "")]
[InlineData("http://localhost", "http", "localhost", 80, "")]
[InlineData("http://www.example.com", "http", "www.example.com", 80, "")]
[InlineData("https://www.example.com", "https", "www.example.com", 443, "")]
[InlineData("http://www.example.com/", "http", "www.example.com", 80, "")]
[InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", 80, "/foo?bar=baz")]
[InlineData("http://www.example.com:5000", "http", "www.example.com", 5000, "")]
[InlineData("https://www.example.com:5000", "https", "www.example.com", 5000, "")]
[InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "")]
[InlineData("http://www.example.com:NOTAPORT", "http", "www.example.com:NOTAPORT", 80, "")]
[InlineData("https://www.example.com:NOTAPORT", "https", "www.example.com:NOTAPORT", 443, "")]
[InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "")]
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter")]
[InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/tmp/kestrel-test.sock")]
[InlineData("http://unix:5000/tmp/kestrel-test.sock", "http", "unix", 5000, "/tmp/kestrel-test.sock")]
[InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "")]
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter")]
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase)
[InlineData("5000", "http", "+", 5000, "/", "http://+:5000/")]
[InlineData("://emptyscheme", "", "emptyscheme", 0, "", "://emptyscheme:0")]
[InlineData("http://localhost", "http", "localhost", 80, "", "http://localhost:80")]
[InlineData("http://www.example.com", "http", "www.example.com", 80, "", "http://www.example.com:80")]
[InlineData("https://www.example.com", "https", "www.example.com", 443, "", "https://www.example.com:443")]
[InlineData("http://www.example.com/", "http", "www.example.com", 80, "", "http://www.example.com:80")]
[InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", 80, "/foo?bar=baz", "http://www.example.com:80/foo?bar=baz")]
[InlineData("http://www.example.com:5000", "http", "www.example.com", 5000, "", null)]
[InlineData("https://www.example.com:5000", "https", "www.example.com", 5000, "", null)]
[InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "", "http://www.example.com:5000")]
[InlineData("http://www.example.com:NOTAPORT", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
[InlineData("https://www.example.com:NOTAPORT", "https", "www.example.com:NOTAPORT", 443, "", "https://www.example.com:notaport:443")]
[InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter", "http://foo::80/tmp/kestrel-test.sock:5000/doesn't/matter")]
[InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/tmp/kestrel-test.sock", "http://unix:foo:80/tmp/kestrel-test.sock")]
[InlineData("http://unix:5000/tmp/kestrel-test.sock", "http", "unix", 5000, "/tmp/kestrel-test.sock", null)]
[InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "", null)]
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "", null)]
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter", null)]
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
{
var serverAddress = ServerAddress.FromUrl(url);
@ -47,6 +48,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(host, serverAddress.Host);
Assert.Equal(port, serverAddress.Port);
Assert.Equal(pathBase, serverAddress.PathBase);
Assert.Equal(toString ?? url, serverAddress.ToString());
}
[Fact]

View File

@ -6,7 +6,6 @@ using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Http;
using Microsoft.AspNetCore.Server.Kestrel.TestCommon;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
public TestServer(RequestDelegate app, ServiceContext context)
: this(app, context, $"http://localhost:{GetNextPort()}/")
: this(app, context, "http://localhost:0/")
{
}
@ -58,10 +57,5 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
_server.Dispose();
_engine.Dispose();
}
public static int GetNextPort()
{
return PortManager.GetNextPort();
}
}
}

View File

@ -5,7 +5,6 @@
"Microsoft.NETCore.Platforms": "1.0.1-*",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.0.0-*",
"Microsoft.AspNetCore.Server.Kestrel.TestCommon": "1.0.0-*",
"Microsoft.AspNetCore.Testing": "1.0.0-*",
"xunit": "2.1.0"
},