From 1f50f4c2a87c961482c0922a56182f36d622da41 Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 5 Oct 2015 15:14:18 -0700 Subject: [PATCH] #231 bind to IPv6Any, add functional tests. --- KestrelHttpServer.sln | 7 ++ .../Networking/UvTcpHandle.cs | 2 +- .../AddressRegistrationTests.cs | 75 +++++++++++++++++++ ...spNet.Server.Kestrel.FunctionalTests.xproj | 21 ++++++ .../RequestTests.cs | 71 ++++++++++++++++++ .../ResponseTests.cs | 73 ++++++++++++++++++ .../ThreadCountTests.cs | 74 ++++++++++++++++++ .../project.json | 19 +++++ .../CreateIPEndpointTests.cs | 7 +- .../TestInput.cs | 6 +- 10 files changed, 349 insertions(+), 6 deletions(-) create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/Microsoft.AspNet.Server.Kestrel.FunctionalTests.xproj create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ResponseTests.cs create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ThreadCountTests.cs create mode 100644 test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/project.json diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index f2da75a88c..a0670dfc2a 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -35,6 +35,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kes EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kestrel.Https", "src\Microsoft.AspNet.Server.Kestrel.Https\Microsoft.AspNet.Server.Kestrel.Https.xproj", "{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kestrel.FunctionalTests", "test\Microsoft.AspNet.Server.Kestrel.FunctionalTests\Microsoft.AspNet.Server.Kestrel.FunctionalTests.xproj", "{9559A5F1-080C-4909-B6CF-7E4B3DC55748}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,10 @@ Global {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|Any CPU.Build.0 = Release|Any CPU + {9559A5F1-080C-4909-B6CF-7E4B3DC55748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -86,5 +92,6 @@ Global {82295647-7C1C-4671-BAB6-0FEF58F949EC} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD} {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} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs index c5d4e25e63..4e79ea9864 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking } else { - ip = IPAddress.Any; + ip = IPAddress.IPv6Any; } } diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs new file mode 100644 index 0000000000..b7b7b03877 --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -0,0 +1,75 @@ +// 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.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Extensions; +using Microsoft.Dnx.Runtime.Infrastructure; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests +{ + public class AddressRegistrationTests + { + [Theory, MemberData(nameof(AddressRegistrationData))] + public async Task RegisterAddresses_Success(string addressInput, string[] testUrls) + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "server.urls", addressInput } + }) + .Build(); + + var hostBuilder = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config); + hostBuilder.UseServer("Microsoft.AspNet.Server.Kestrel"); + hostBuilder.UseStartup(ConfigureEchoAddress); + + using (var app = hostBuilder.Build().Start()) + { + using (var client = new HttpClient()) + { + foreach (var testUrl in testUrls) + { + var responseText = await client.GetStringAsync(testUrl); + Assert.Equal(testUrl, responseText); + } + } + } + } + + public static TheoryData AddressRegistrationData + { + get + { + var dataset = new TheoryData(); + dataset.Add("8787", new[] { "http://localhost:8787/" }); + dataset.Add("8787;8788", new[] { "http://localhost:8787/", "http://localhost:8788/" }); + dataset.Add("http://*:8787/", new[] { "http://localhost:8787/", "http://127.0.0.1:8787/", "http://[::1]:8787/" }); + dataset.Add("http://localhost:8787/", new[] { "http://localhost:8787/", "http://127.0.0.1:8787/", + /* // https://github.com/aspnet/KestrelHttpServer/issues/231 + "http://[::1]:8787/" + */ }); + dataset.Add("http://127.0.0.1:8787/", new[] { "http://127.0.0.1:8787/", }); + dataset.Add("http://[::1]:8787/", new[] { "http://[::1]:8787/", }); + dataset.Add("http://127.0.0.1:8787/;http://[::1]:8787/", new[] { "http://127.0.0.1:8787/", "http://[::1]:8787/" }); + dataset.Add("http://localhost:8787/base/path", new[] { "http://localhost:8787/base/path" }); + + return dataset; + } + } + + private void ConfigureEchoAddress(IApplicationBuilder app) + { + app.Run(context => + { + return context.Response.WriteAsync(context.Request.GetDisplayUrl()); + }); + } + } +} diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/Microsoft.AspNet.Server.Kestrel.FunctionalTests.xproj b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/Microsoft.AspNet.Server.Kestrel.FunctionalTests.xproj new file mode 100644 index 0000000000..d8807f6583 --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/Microsoft.AspNet.Server.Kestrel.FunctionalTests.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 9559a5f1-080c-4909-b6cf-7e4b3dc55748 + Microsoft.AspNet.Server.Kestrel.FunctionalTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs new file mode 100644 index 0000000000..5819fc9738 --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/RequestTests.cs @@ -0,0 +1,71 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Http; +using Microsoft.Dnx.Runtime.Infrastructure; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests +{ + public class RequestTests + { + [Fact(Skip = "https://github.com/aspnet/KestrelHttpServer/issues/234")] + public async Task LargeUpload() + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "server.urls", "http://localhost:8791/" } + }) + .Build(); + + var hostBuilder = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config); + hostBuilder.UseServer("Microsoft.AspNet.Server.Kestrel"); + hostBuilder.UseStartup(app => + { + app.Run(async context => + { + // Read the full request body + var total = 0; + var bytes = new byte[1024]; + var count = await context.Request.Body.ReadAsync(bytes, 0, bytes.Length); + while (count > 0) + { + for (int i = 0; i < count; i++) + { + Assert.Equal(total % 256, bytes[i]); + total++; + } + count = await context.Request.Body.ReadAsync(bytes, 0, bytes.Length); + } + + await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture)); + }); + }); + + using (var app = hostBuilder.Build().Start()) + { + using (var client = new HttpClient()) + { + var bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)i; + } + + var response = await client.PostAsync("http://localhost:8791/", new ByteArrayContent(bytes)); + response.EnsureSuccessStatusCode(); + var sizeString = await response.Content.ReadAsStringAsync(); + Assert.Equal(sizeString, bytes.Length.ToString(CultureInfo.InvariantCulture)); + } + } + } + } +} diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ResponseTests.cs b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ResponseTests.cs new file mode 100644 index 0000000000..a31c2374fa --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ResponseTests.cs @@ -0,0 +1,73 @@ +// 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.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.Dnx.Runtime.Infrastructure; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests +{ + public class ResponseTests + { + [Fact] + public async Task LargeDownload() + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "server.urls", "http://localhost:8792/" } + }) + .Build(); + + var hostBuilder = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config); + hostBuilder.UseServer("Microsoft.AspNet.Server.Kestrel"); + hostBuilder.UseStartup(app => + { + app.Run(async context => + { + var bytes = new byte[1024]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)i; + } + + context.Response.ContentLength = bytes.Length * 1024; + + for (int i = 0; i < 1024; i++) + { + await context.Response.Body.WriteAsync(bytes, 0, bytes.Length); + } + }); + }); + + using (var app = hostBuilder.Build().Start()) + { + using (var client = new HttpClient()) + { + var response = await client.GetAsync("http://localhost:8792/"); + response.EnsureSuccessStatusCode(); + var responseBody = await response.Content.ReadAsStreamAsync(); + + // Read the full response body + var total = 0; + var bytes = new byte[1024]; + var count = await responseBody.ReadAsync(bytes, 0, bytes.Length); + while (count > 0) + { + for (int i = 0; i < count; i++) + { + Assert.Equal(total % 256, bytes[i]); + total++; + } + count = await responseBody.ReadAsync(bytes, 0, bytes.Length); + } + } + } + } + } +} diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ThreadCountTests.cs b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ThreadCountTests.cs new file mode 100644 index 0000000000..d47679f540 --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/ThreadCountTests.cs @@ -0,0 +1,74 @@ +// 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.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Features; +using Microsoft.Dnx.Runtime.Infrastructure; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests +{ + public class ThreadCountTests + { + [Theory(Skip = "https://github.com/aspnet/KestrelHttpServer/issues/232"), MemberData(nameof(OneToTen))] + public async Task ZeroToTenThreads(int threadCount) + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "server.urls", "http://localhost:8790/" } + }) + .Build(); + + var hostBuilder = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config); + hostBuilder.UseServer("Microsoft.AspNet.Server.Kestrel"); + hostBuilder.UseStartup(app => + { + var serverInfo = app.ServerFeatures.Get(); + serverInfo.ThreadCount = threadCount; + app.Run(context => + { + return context.Response.WriteAsync("Hello World"); + }); + }); + + using (var app = hostBuilder.Build().Start()) + { + using (var client = new HttpClient()) + { + // Send 20 requests just to make sure we don't get any failures + var requestTasks = new List>(); + for (int i = 0; i < 20; i++) + { + var requestTask = client.GetStringAsync("http://localhost:8790/"); + requestTasks.Add(requestTask); + } + + foreach (var result in await Task.WhenAll(requestTasks)) + { + Assert.Equal("Hello World", result); + } + } + } + } + + public static TheoryData OneToTen + { + get + { + var dataset = new TheoryData(); + for (int i = 1; i <= 10; i++) + { + dataset.Add(i); + } + return dataset; + } + } + } +} diff --git a/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/project.json b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/project.json new file mode 100644 index 0000000000..7ecc7fb8ab --- /dev/null +++ b/test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-*", + "System.Net.Http": "4.0.1-beta-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + }, + "compilationOptions": { + "allowUnsafe": true + }, + "commands": { + "test": "xunit.runner.aspnet -parallel none" + } +} diff --git a/test/Microsoft.AspNet.Server.KestrelTests/CreateIPEndpointTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/CreateIPEndpointTests.cs index 5cc5c2345c..e8a14e55b8 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/CreateIPEndpointTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/CreateIPEndpointTests.cs @@ -11,12 +11,13 @@ namespace Microsoft.AspNet.Server.KestrelTests public class CreateIPEndpointTests { [Theory] - [InlineData("localhost", "127.0.0.1")] + [InlineData("localhost", "127.0.0.1")] // https://github.com/aspnet/KestrelHttpServer/issues/231 [InlineData("10.10.10.10", "10.10.10.10")] - [InlineData("randomhost", "0.0.0.0")] + [InlineData("[::1]", "::1")] + [InlineData("randomhost", "::")] // "::" is IPAddress.IPv6Any + [InlineData("*", "::")] // "::" is IPAddress.IPv6Any public void CorrectIPEndpointsAreCreated(string host, string expectedAddress) { - // "0.0.0.0" is IPAddress.Any var endpoint = UvTcpHandle.CreateIPEndpoint(ServerAddress.FromUrl($"http://{host}:5000/")); Assert.NotNull(endpoint); Assert.Equal(IPAddress.Parse(expectedAddress), endpoint.Address); diff --git a/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs b/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs index 47470bb4fc..3db112560c 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs @@ -66,16 +66,18 @@ namespace Microsoft.AspNet.Server.KestrelTests { } - async Task IFrameControl.WriteAsync(ArraySegment data, CancellationToken cancellationToken) + Task IFrameControl.WriteAsync(ArraySegment data, CancellationToken cancellationToken) { + return Task.FromResult(0); } void IFrameControl.Flush() { } - async Task IFrameControl.FlushAsync(CancellationToken cancellationToken) + Task IFrameControl.FlushAsync(CancellationToken cancellationToken) { + return Task.FromResult(0); } } }