diff --git a/src/ServerTests/.gitignore b/src/ServerTests/.gitignore new file mode 100644 index 0000000000..a7760db659 --- /dev/null +++ b/src/ServerTests/.gitignore @@ -0,0 +1,31 @@ +[Oo]bj/ +[Bb]in/ +TestResults/ +.nuget/ +*.sln.ide/ +_ReSharper.*/ +packages/ +artifacts/ +PublishProfiles/ +*.user +*.suo +*.cache +*.docstates +_ReSharper.* +nuget.exe +*net45.csproj +*net451.csproj +*k10.csproj +*.psess +*.vsp +*.pidb +*.userprefs +*DS_Store +*.ncrunchsolution +*.*sdf +*.ipch +project.lock.json +/.vs/ +.testPublish/ +.build/ +global.json diff --git a/src/ServerTests/Directory.Build.props b/src/ServerTests/Directory.Build.props new file mode 100644 index 0000000000..fc93574bae --- /dev/null +++ b/src/ServerTests/Directory.Build.props @@ -0,0 +1,22 @@ + + + + + + + + + Microsoft ASP.NET Core + https://github.com/aspnet/servertests + git + $(MSBuildThisFileDirectory) + true + + + + + + + diff --git a/src/ServerTests/Directory.Build.targets b/src/ServerTests/Directory.Build.targets new file mode 100644 index 0000000000..53b3f6e1da --- /dev/null +++ b/src/ServerTests/Directory.Build.targets @@ -0,0 +1,7 @@ + + + $(MicrosoftNETCoreApp20PackageVersion) + $(MicrosoftNETCoreApp21PackageVersion) + $(NETStandardLibrary20PackageVersion) + + diff --git a/src/ServerTests/NuGetPackageVerifier.json b/src/ServerTests/NuGetPackageVerifier.json new file mode 100644 index 0000000000..22ef3c09c0 --- /dev/null +++ b/src/ServerTests/NuGetPackageVerifier.json @@ -0,0 +1,7 @@ +{ + "Default": { + "rules": [ + "DefaultCompositeRule" + ] + } +} diff --git a/src/ServerTests/README.md b/src/ServerTests/README.md new file mode 100644 index 0000000000..355a989132 --- /dev/null +++ b/src/ServerTests/README.md @@ -0,0 +1,9 @@ +Server Tests +============ + +[![Travis build status](https://img.shields.io/travis/aspnet/ServerTests.svg?label=travis-ci&branch=dev&style=flat-square)](https://travis-ci.org/aspnet/ServerTests/branches) +[![AppVeyor build status](https://img.shields.io/appveyor/ci/aspnetci/ServerTests/dev.svg?label=appveyor&style=flat-square)](https://ci.appveyor.com/project/aspnetci/ServerTests/branch/dev) + +This repo hosts [HttpSysServer](https://github.com/aspnet/HttpSysServer) and [Kestrel](https://github.com/aspnet/KestrelHttpServer) tests. + +This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. diff --git a/src/ServerTests/ServerTests.sln b/src/ServerTests/ServerTests.sln new file mode 100644 index 0000000000..bd58846053 --- /dev/null +++ b/src/ServerTests/ServerTests.sln @@ -0,0 +1,50 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 +MinimumVisualStudioVersion = 15.0.26730.03 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{49AB8AAA-8160-48DF-A18B-78F51E54E02A}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + NuGet.config = NuGet.config + build\repo.props = build\repo.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FA91F388-F4AF-4850-9D68-D4D128E6B1A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerComparison.FunctionalTests", "test\ServerComparison.FunctionalTests\ServerComparison.FunctionalTests.csproj", "{A319ACCE-060B-4385-9534-9F2202F6180E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerComparison.TestSites", "test\ServerComparison.TestSites\ServerComparison.TestSites.csproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{55694E45-5EDE-46F8-80AA-797DE5F8C5C3}" + ProjectSection(SolutionItems) = preProject + build\dependencies.props = build\dependencies.props + build\repo.props = build\repo.props + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A319ACCE-060B-4385-9534-9F2202F6180E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A319ACCE-060B-4385-9534-9F2202F6180E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A319ACCE-060B-4385-9534-9F2202F6180E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A319ACCE-060B-4385-9534-9F2202F6180E}.Release|Any CPU.Build.0 = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A319ACCE-060B-4385-9534-9F2202F6180E} = {FA91F388-F4AF-4850-9D68-D4D128E6B1A6} + {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {FA91F388-F4AF-4850-9D68-D4D128E6B1A6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8A313020-8407-494F-81D7-7631580C5FCC} + EndGlobalSection +EndGlobal diff --git a/src/ServerTests/build/dependencies.props b/src/ServerTests/build/dependencies.props new file mode 100644 index 0000000000..72cad24822 --- /dev/null +++ b/src/ServerTests/build/dependencies.props @@ -0,0 +1,39 @@ + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + 2.1.3-rtm-15802 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 0.5.1 + 2.1.2 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.0.0 + 2.1.2 + 2.1.1 + 15.6.1 + 2.0.3 + 1.4.0 + 3.2.0 + 2.3.1 + 2.4.0-beta.1.build3945 + + + + + + + + diff --git a/src/ServerTests/build/repo.props b/src/ServerTests/build/repo.props new file mode 100644 index 0000000000..c8bd413e1e --- /dev/null +++ b/src/ServerTests/build/repo.props @@ -0,0 +1,18 @@ + + + + + + + + + Internal.AspNetCore.Universe.Lineup + 2.1.0-rc1-* + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json + + + + + + + diff --git a/src/ServerTests/build/sources.props b/src/ServerTests/build/sources.props new file mode 100644 index 0000000000..9215df9751 --- /dev/null +++ b/src/ServerTests/build/sources.props @@ -0,0 +1,17 @@ + + + + + $(DotNetRestoreSources) + + $(RestoreSources); + https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + + + $(RestoreSources); + https://api.nuget.org/v3/index.json; + + + diff --git a/src/ServerTests/install-nginx.sh b/src/ServerTests/install-nginx.sh new file mode 100644 index 0000000000..9564813c2d --- /dev/null +++ b/src/ServerTests/install-nginx.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +curl -sSL http://nginx.org/download/nginx-1.8.0.tar.gz | tar zxfv - -C /tmp && cd /tmp/nginx-1.8.0/ +./configure --prefix=$HOME/nginxinstall --with-http_ssl_module +make +make install diff --git a/src/ServerTests/test/Directory.Build.props b/src/ServerTests/test/Directory.Build.props new file mode 100644 index 0000000000..620b803d21 --- /dev/null +++ b/src/ServerTests/test/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + netcoreapp2.1;netcoreapp2.0 + $(DeveloperBuildTestTfms) + $(StandardTestTfms);net461 + + diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/HelloWorldTest.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/HelloWorldTest.cs new file mode 100644 index 0000000000..62e1fcfbd8 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/HelloWorldTest.cs @@ -0,0 +1,118 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace ServerComparison.FunctionalTests +{ + public class HelloWorldTests : LoggedTest + { + public HelloWorldTests(ITestOutputHelper output) : base(output) + { + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task HelloWorld_WebListener(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return HelloWorld(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + public Task HelloWorld_IISExpress(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return HelloWorld(ServerType.IISExpress, runtimeFlavor, RuntimeArchitecture.x64, applicationType, hostingModel: hostingModel, additionalPublishParameters: additionalPublishParameters); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + public Task HelloWorld_Kestrel_Clr(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return HelloWorld(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task HelloWorld_Kestrel(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return HelloWorld(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task HelloWorld_Nginx(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return HelloWorld(ServerType.Nginx, runtimeFlavor, RuntimeArchitecture.x64, applicationType); + } + + + private async Task HelloWorld(ServerType serverType, + RuntimeFlavor runtimeFlavor, + RuntimeArchitecture architecture, + ApplicationType applicationType, + [CallerMemberName] string testName = null, + HostingModel hostingModel = HostingModel.OutOfProcess, + string additionalPublishParameters = "") + { + testName = $"{testName}_{serverType}_{runtimeFlavor}_{architecture}_{applicationType}"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorld"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, runtimeFlavor, architecture) + { + EnvironmentName = "HelloWorld", // Will pick the Start class named 'StartupHelloWorld', + ServerConfigTemplateContent = Helpers.GetConfigContent(serverType, "Http.config", "nginx.conf"), + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = Helpers.GetTargetFramework(runtimeFlavor), + ApplicationType = applicationType, + HostingModel = hostingModel, + AdditionalPublishParameters = additionalPublishParameters + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync(string.Empty); + }, logger, deploymentResult.HostShutdownToken); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Hello World", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + } +} diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/Helpers.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/Helpers.cs new file mode 100644 index 0000000000..0c4c4ab89d --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/Helpers.cs @@ -0,0 +1,69 @@ +// 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.Server.IntegrationTesting; + +namespace ServerComparison.FunctionalTests +{ + public class Helpers + { + public static string GetApplicationPath(ApplicationType applicationType) + { + var applicationBasePath = AppContext.BaseDirectory; + + var directoryInfo = new DirectoryInfo(applicationBasePath); + do + { + var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "ServerTests.sln")); + if (solutionFileInfo.Exists) + { + return Path.GetFullPath(Path.Combine(directoryInfo.FullName, "test", "ServerComparison.TestSites")); + } + + directoryInfo = directoryInfo.Parent; + } + while (directoryInfo.Parent != null); + + throw new Exception($"Solution root could not be found using {applicationBasePath}"); + } + + public static string GetConfigContent(ServerType serverType, string iisConfig, string nginxConfig) + { + var applicationBasePath = AppContext.BaseDirectory; + + string content = null; + if (serverType == ServerType.IISExpress) + { + content = File.ReadAllText(Path.Combine(applicationBasePath, iisConfig)); + } + else if (serverType == ServerType.Nginx) + { + content = File.ReadAllText(Path.Combine(applicationBasePath, nginxConfig)); + } + + return content; + } + + public static string GetTargetFramework(RuntimeFlavor runtimeFlavor) + { + if (runtimeFlavor == RuntimeFlavor.Clr) + { + return "net461"; + } + else if (runtimeFlavor == RuntimeFlavor.CoreClr) + { +#if NETCOREAPP2_0 + return "netcoreapp2.0"; +#elif NETCOREAPP2_1 || NET461 + return "netcoreapp2.1"; +#else +#error Target frameworks need to be updated. +#endif + } + + throw new ArgumentException($"Unknown RuntimeFlavor '{runtimeFlavor}'"); + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/Http.config b/src/ServerTests/test/ServerComparison.FunctionalTests/Http.config new file mode 100644 index 0000000000..8e8b3f2f9d --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/Http.config @@ -0,0 +1,1029 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.conf b/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.conf new file mode 100644 index 0000000000..1fa5e2f1e7 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.conf @@ -0,0 +1,36 @@ +error_log [errorlog]; +user [user]; +worker_processes 4; +pid [pidFile]; + +events { + worker_connections 768; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 10; + types_hash_max_size 2048; + + default_type application/octet-stream; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + access_log [accesslog]; + + gzip off; + + server { + listen [listenPort]; + location / { + proxy_pass [redirectUri]; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } +} diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.config b/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.config new file mode 100644 index 0000000000..c9b9f970be --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/NoCompression.config @@ -0,0 +1,1021 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthentication.config b/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthentication.config new file mode 100644 index 0000000000..c35bcf3443 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthentication.config @@ -0,0 +1,1041 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthenticationTest.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthenticationTest.cs new file mode 100644 index 0000000000..0aff3407db --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/NtlmAuthenticationTest.cs @@ -0,0 +1,119 @@ +// 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.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace ServerComparison.FunctionalTests +{ + public class NtlmAuthenticationTests : LoggedTest + { + public NtlmAuthenticationTests(ITestOutputHelper output) : base(output) + { + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(ServerType.IISExpress, RuntimeFlavor.Clr, "net461", RuntimeArchitecture.x64, ApplicationType.Portable, HostingModel.OutOfProcess, "V2", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(ServerType.IISExpress, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(ServerType.WebListener, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.WebListener, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.WebListener, RuntimeFlavor.CoreClr, "netcoreapp2.0", RuntimeArchitecture.x64, ApplicationType.Standalone)] + [InlineData(ServerType.WebListener, RuntimeFlavor.CoreClr, "netcoreapp2.1", RuntimeArchitecture.x64, ApplicationType.Standalone)] + public async Task NtlmAuthentication(ServerType serverType, + RuntimeFlavor runtimeFlavor, + string targetFramework, + RuntimeArchitecture architecture, + ApplicationType applicationType, + HostingModel hostingModel = HostingModel.OutOfProcess, + string additionalPublishParameters = "") + { + var testName = $"NtlmAuthentication_{serverType}_{runtimeFlavor}_{architecture}_{applicationType}"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("NtlmAuthenticationTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, runtimeFlavor, architecture) + { + EnvironmentName = "NtlmAuthentication", // Will pick the Start class named 'StartupNtlmAuthentication' + ServerConfigTemplateContent = Helpers.GetConfigContent(serverType, "NtlmAuthentication.config", nginxConfig: null), + SiteName = "NtlmAuthenticationTestSite", // This is configured in the NtlmAuthentication.config + TargetFramework = targetFramework, + ApplicationType = applicationType, + HostingModel = hostingModel, + AdditionalPublishParameters = additionalPublishParameters + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + var httpClient = deploymentResult.HttpClient; + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return httpClient.GetAsync(string.Empty); + }, logger, deploymentResult.HostShutdownToken); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Hello World", responseText); + + logger.LogInformation("Testing /Anonymous"); + response = await httpClient.GetAsync("/Anonymous"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Anonymous?True", responseText); + + logger.LogInformation("Testing /Restricted"); + response = await httpClient.GetAsync("/Restricted"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); + + logger.LogInformation("Testing /Forbidden"); + response = await httpClient.GetAsync("/Forbidden"); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + + logger.LogInformation("Enabling Default Credentials"); + + // Change the http client to one that uses default credentials + httpClient = deploymentResult.CreateHttpClient(new HttpClientHandler() { UseDefaultCredentials = true }); + + logger.LogInformation("Testing /Restricted"); + response = await httpClient.GetAsync("/Restricted"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Authenticated", responseText); + + logger.LogInformation("Testing /Forbidden"); + response = await httpClient.GetAsync("/Forbidden"); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/Properties/AssemblyInfo.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b1fa884228 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseCompressionTests.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseCompressionTests.cs new file mode 100644 index 0000000000..301a0b74e6 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseCompressionTests.cs @@ -0,0 +1,331 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace ServerComparison.FunctionalTests +{ + public class ResponseCompressionTests : LoggedTest + { + // NGinx's default min size is 20 bytes + private static readonly string HelloWorldBody = "Hello World;" + new string('a', 20); + + public ResponseCompressionTests(ITestOutputHelper output) : base(output) + { + } + + // IIS Express + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + public Task ResponseCompression_IISExpress_NoCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseCompression(ServerType.IISExpress, + runtimeFlavor, + RuntimeArchitecture.x64, + CheckNoCompressionAsync, + applicationType, + hostCompression: false, + hostingModel: hostingModel, + additionalPublishParameters: additionalPublishParameters); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + public Task ResponseCompression_IISExpress_HostCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseCompression(ServerType.IISExpress, + runtimeFlavor, + RuntimeArchitecture.x64, + CheckHostCompressionAsync, + applicationType, + hostCompression: true, + hostingModel: hostingModel, + additionalPublishParameters: additionalPublishParameters); + } + + [ConditionalTheory(Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + public Task ResponseCompression_IISExpress_AppCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseCompression(ServerType.IISExpress, + runtimeFlavor, + RuntimeArchitecture.x64, + CheckAppCompressionAsync, + applicationType, + hostCompression: true, + hostingModel: hostingModel, + additionalPublishParameters: additionalPublishParameters); + } + + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + public Task ResponseCompression_IISExpress_AppAndHostCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseCompression(ServerType.IISExpress, + runtimeFlavor, + RuntimeArchitecture.x64, + CheckAppCompressionAsync, + applicationType, + hostCompression: true, + hostingModel: hostingModel, + additionalPublishParameters: additionalPublishParameters); + } + + // WebListener + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseCompression_WebListener_NoCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseCompression(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckNoCompressionAsync, applicationType, hostCompression: false); + } + + // WebListener doesn't support HostCompression + // "The archive entry was compressed using an unsupported compression method." + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseCompression_WebListener_AppCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseCompression(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckAppCompressionAsync, applicationType, hostCompression: false); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseCompression_WebListener_AppAndHostCompression(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseCompression(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckAppCompressionAsync, applicationType, hostCompression: true); + } + + // Kestrel + [Theory] + [InlineData(ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Kestrel_NoCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckNoCompressionAsync, applicationType, hostCompression: false); + } + + [Theory] + [InlineData(ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Kestrel_AppCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckAppCompressionAsync, applicationType, hostCompression: false); + } + + // Nginx + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Nginx_NoCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckNoCompressionAsync, applicationType, hostCompression: false); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Nginx_HostCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckHostCompressionAsync, applicationType, hostCompression: true); + } + + [ConditionalTheory(Skip = "No pass-through compression https://github.com/aspnet/BasicMiddleware/issues/123")] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Nginx_AppCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckHostCompressionAsync, applicationType, hostCompression: false); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Portable)] + [InlineData(ServerType.Nginx, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64, ApplicationType.Standalone)] + public Task ResponseCompression_Nginx_AppAndHostCompression(ServerType serverType, RuntimeFlavor runtimeFlavor, RuntimeArchitecture architecture, ApplicationType applicationType) + { + return ResponseCompression(serverType, runtimeFlavor, architecture, CheckAppCompressionAsync, applicationType, hostCompression: true); + } + + private async Task ResponseCompression(ServerType serverType, + RuntimeFlavor runtimeFlavor, + RuntimeArchitecture architecture, + Func scenario, + ApplicationType applicationType, + bool hostCompression, + [CallerMemberName] string testName = null, + HostingModel hostingModel = HostingModel.OutOfProcess, + string additionalPublishParameters = "") + { + testName = $"{testName}_{serverType}_{runtimeFlavor}_{architecture}_{applicationType}"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("ResponseCompression"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, runtimeFlavor, architecture) + { + EnvironmentName = "ResponseCompression", + ServerConfigTemplateContent = Helpers.GetConfigContent(serverType, + hostCompression ? "http.config" : "NoCompression.config", + hostCompression ? "nginx.conf" : "NoCompression.conf"), + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = Helpers.GetTargetFramework(runtimeFlavor), + ApplicationType = applicationType, + HostingModel = hostingModel, + AdditionalPublishParameters = additionalPublishParameters + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + var httpClientHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None }; + Assert.True(httpClientHandler.SupportsAutomaticDecompression); + var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return httpClient.GetAsync(string.Empty); + }, logger, deploymentResult.HostShutdownToken); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Running", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + + await scenario(httpClient, logger); + } + } + } + + private static async Task CheckNoCompressionAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing /NoAppCompression"); + var request = new HttpRequestMessage(HttpMethod.Get, "NoAppCompression"); + request.Headers.AcceptEncoding.ParseAdd("gzip,deflate"); + var response = await client.SendAsync(request); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal(HelloWorldBody, responseText); + Assert.Equal(HelloWorldBody.Length.ToString(), GetContentLength(response)); + Assert.Equal(0, response.Content.Headers.ContentEncoding.Count); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static Task CheckHostCompressionAsync(HttpClient client, ILogger logger) + { + return CheckCompressionAsync(client, "NoAppCompression", logger); + } + + private static Task CheckAppCompressionAsync(HttpClient client, ILogger logger) + { + return CheckCompressionAsync(client, "AppCompression", logger); + } + + private static async Task CheckCompressionAsync(HttpClient client, string url, ILogger logger) + { + // Manage the compression manually because HttpClient removes the Content-Encoding header when decompressing. + logger.LogInformation($"Testing /{url}"); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.AcceptEncoding.ParseAdd("gzip,deflate"); + var response = await client.SendAsync(request); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + responseText = await ReadCompressedAsStringAsync(response.Content); + Assert.Equal(HelloWorldBody, responseText); + Assert.Equal(1, response.Content.Headers.ContentEncoding.Count); + Assert.Equal("gzip", response.Content.Headers.ContentEncoding.First()); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static string GetContentLength(HttpResponseMessage response) + { + // Don't use response.Content.Headers.ContentLength, it will dynamically calculate the value if it can. + return response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var values) ? values.FirstOrDefault() : null; + } + + private static async Task ReadCompressedAsStringAsync(HttpContent content) + { + using (var stream = await content.ReadAsStreamAsync()) + using (var compressStream = new GZipStream(stream, CompressionMode.Decompress)) + using (var reader = new StreamReader(compressStream)) + { + return await reader.ReadToEndAsync(); + } + } + } +} diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseTests.cs b/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseTests.cs new file mode 100644 index 0000000000..62d7fec6e3 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/ResponseTests.cs @@ -0,0 +1,406 @@ +// 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.Http; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace ServerComparison.FunctionalTests +{ + public class ResponseTests : LoggedTest + { + public ResponseTests(ITestOutputHelper output) : base(output) + { + } + + // IIS Express + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable, HostingModel.OutOfProcess, "", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + public Task ResponseFormats_IISExpress_ContentLength(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseFormats(ServerType.IISExpress, runtimeFlavor, RuntimeArchitecture.x64, CheckContentLengthAsync, applicationType, hostingModel: hostingModel, additionalPublishParameters: additionalPublishParameters); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable, HostingModel.OutOfProcess, "", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + public Task ResponseFormats_IISExpress_Chunked(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseFormats(ServerType.IISExpress, runtimeFlavor, RuntimeArchitecture.x64, CheckChunkedAsync, applicationType, hostingModel: hostingModel, additionalPublishParameters: additionalPublishParameters); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable, HostingModel.OutOfProcess, "", Skip = "Websdk issue with full framework publish. See https://github.com/aspnet/websdk/pull/322")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V1")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone, HostingModel.OutOfProcess, "/p:ANCMVersion=V2")] + public Task ResponseFormats_IIS_ManuallyChunk(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, HostingModel hostingModel, string additionalPublishParameters) + { + return ResponseFormats(ServerType.IISExpress, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAsync, applicationType, hostingModel: hostingModel, additionalPublishParameters: additionalPublishParameters); + } + + // Weblistener + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_WebListener_ContentLength(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckContentLengthAsync, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_WebListener_Chunked(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckChunkedAsync, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + // IIS will remove the "Connection: close" header https://github.com/aspnet/IISIntegration/issues/7 + public Task ResponseFormats_WebListener_Http10ConnectionClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckHttp10ConnectionCloseAsync, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] // https://github.com/aspnet/WebListener/issues/259 + // IIS will remove the "Connection: close" header https://github.com/aspnet/IISIntegration/issues/7 + public Task ResponseFormats_WebListener_Http11ConnectionClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckHttp11ConnectionCloseAsync, applicationType); + } + + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_WebListener_ManuallyChunk(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAsync, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_WebListener_ManuallyChunkAndClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.WebListener, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAndCloseAsync, applicationType); + } + + // Kestrel + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_ContentLength(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckContentLengthAsync, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_Http10ConnectionClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckHttp10ConnectionCloseAsync, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_Http11ConnectionClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckHttp11ConnectionCloseAsync, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_Chunked(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckChunkedAsync, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_ManuallyChunk(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAsync, applicationType); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Kestrel_ManuallyChunkAndClose(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Kestrel, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAndCloseAsync, applicationType); + } + + // Nginx + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Nginx_ContentLength( RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Nginx, runtimeFlavor, RuntimeArchitecture.x64, CheckContentLengthAsync, applicationType); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Nginx_Chunked(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Nginx, runtimeFlavor, RuntimeArchitecture.x64, CheckChunkedAsync, applicationType); + } + + + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Portable)] + [InlineData(RuntimeFlavor.CoreClr, ApplicationType.Standalone)] + public Task ResponseFormats_Nginx_ManuallyChunk(RuntimeFlavor runtimeFlavor, ApplicationType applicationType) + { + return ResponseFormats(ServerType.Nginx, runtimeFlavor, RuntimeArchitecture.x64, CheckManuallyChunkedAsync, applicationType); + } + + private async Task ResponseFormats(ServerType serverType, + RuntimeFlavor runtimeFlavor, + RuntimeArchitecture architecture, + Func scenario, + ApplicationType applicationType, + [CallerMemberName] string testName = null, + HostingModel hostingModel = HostingModel.OutOfProcess, + string additionalPublishParameters = "") + { + testName = $"{testName}_{serverType}_{runtimeFlavor}_{architecture}_{applicationType}"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("ResponseFormats"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, runtimeFlavor, architecture) + { + EnvironmentName = "Responses", + ServerConfigTemplateContent = Helpers.GetConfigContent(serverType, "Http.config", "nginx.conf"), + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = Helpers.GetTargetFramework(runtimeFlavor), + ApplicationType = applicationType, + HostingModel = hostingModel, + AdditionalPublishParameters = additionalPublishParameters + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync(string.Empty); + }, logger, deploymentResult.HostShutdownToken); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Running", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + + await scenario(deploymentResult.HttpClient, logger); + } + } + } + + private static async Task CheckContentLengthAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing ContentLength"); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, "contentlength") + { + Version = new Version(1, 1) + }; + + var response = await client.SendAsync(requestMessage); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Content Length", responseText); + Assert.Null(response.Headers.TransferEncodingChunked); + Assert.Null(response.Headers.ConnectionClose); + Assert.Equal("14", GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static async Task CheckHttp11ConnectionCloseAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing Http11ConnectionClose"); + var response = await client.GetAsync("connectionclose"); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Connnection Close", responseText); + Assert.True(response.Headers.ConnectionClose, "/connectionclose, closed?"); + Assert.True(response.Headers.TransferEncodingChunked); + Assert.Null(GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static async Task CheckHttp10ConnectionCloseAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing Http10ConnectionClose"); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, "connectionclose") + { + Version = new Version(1, 0) + }; + + var response = await client.SendAsync(requestMessage); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Connnection Close", responseText); + Assert.True(response.Headers.ConnectionClose, "/connectionclose, closed?"); + Assert.Null(response.Headers.TransferEncodingChunked); + Assert.Null(GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static async Task CheckChunkedAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing Chunked"); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, "chunked") + { + Version = new Version(1, 1) + }; + + var response = await client.SendAsync(requestMessage); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Chunked", responseText); + Assert.True(response.Headers.TransferEncodingChunked, "/chunked, chunked?"); + Assert.Null(response.Headers.ConnectionClose); + Assert.Null(GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static async Task CheckManuallyChunkedAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing ManuallyChunked"); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, "manuallychunked") + { + Version = new Version(1, 1) + }; + + var response = await client.SendAsync(requestMessage); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Manually Chunked", responseText); + Assert.True(response.Headers.TransferEncodingChunked, "/manuallychunked, chunked?"); + Assert.Null(response.Headers.ConnectionClose); + Assert.Null(GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static async Task CheckManuallyChunkedAndCloseAsync(HttpClient client, ILogger logger) + { + logger.LogInformation("Testing ManuallyChunkedAndClose"); + var response = await client.GetAsync("manuallychunkedandclose"); + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Manually Chunked and Close", responseText); + Assert.True(response.Headers.TransferEncodingChunked, "/manuallychunkedandclose, chunked?"); + Assert.True(response.Headers.ConnectionClose, "/manuallychunkedandclose, closed?"); + Assert.Null(GetContentLength(response)); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + + private static string GetContentLength(HttpResponseMessage response) + { + // Don't use response.Content.Headers.ContentLength, it will dynamically calculate the value if it can. + IEnumerable values; + return response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out values) ? values.FirstOrDefault() : null; + } + } +} diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/ServerComparison.FunctionalTests.csproj b/src/ServerTests/test/ServerComparison.FunctionalTests/ServerComparison.FunctionalTests.csproj new file mode 100644 index 0000000000..ba0fea38e1 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/ServerComparison.FunctionalTests.csproj @@ -0,0 +1,24 @@ + + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + diff --git a/src/ServerTests/test/ServerComparison.FunctionalTests/nginx.conf b/src/ServerTests/test/ServerComparison.FunctionalTests/nginx.conf new file mode 100644 index 0000000000..f9b0bc85c9 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.FunctionalTests/nginx.conf @@ -0,0 +1,37 @@ +error_log [errorlog]; +worker_processes 4; +pid [pidFile]; + +events { + worker_connections 768; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 10; + types_hash_max_size 2048; + + default_type application/octet-stream; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + access_log [accesslog]; + + gzip on; + gzip_types text/plain; + gzip_disable "msie6"; + + server { + listen [listenPort]; + location / { + proxy_pass [redirectUri]; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } +} diff --git a/src/ServerTests/test/ServerComparison.TestSites/Program.cs b/src/ServerTests/test/ServerComparison.TestSites/Program.cs new file mode 100644 index 0000000000..f6ae1163c1 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/Program.cs @@ -0,0 +1,65 @@ +// 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.Hosting; +using Microsoft.AspNetCore.Server.HttpSys; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace ServerComparison.TestSites +{ + public static class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .Build(); + + var builder = new WebHostBuilder() + .UseConfiguration(config) + .ConfigureLogging((_, factory) => + { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Warning); + }) + .UseStartup("ServerComparison.TestSites"); + + // Switch between Kestrel, IIS, and HttpSys for different tests. Default to Kestrel for normal app execution. + if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.HttpSys", StringComparison.Ordinal)) + { + if (string.Equals(builder.GetSetting("environment") ?? + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), + "NtlmAuthentication", System.StringComparison.Ordinal)) + { + // Set up NTLM authentication for HttpSys as follows. + // For IIS and IISExpress use inetmgr to setup NTLM authentication on the application or + // modify the applicationHost.config to enable NTLM. + builder.UseHttpSys(options => + { + options.Authentication.AllowAnonymous = true; + options.Authentication.Schemes = + AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM; + }); + } + else + { + builder.UseHttpSys(); + } + } + else + { + // Check that we are not using IIS inproc before we add Kestrel. + builder.UseKestrel(); + } + + builder.UseIISIntegration(); + + var host = builder.Build(); + + host.Run(); + } + } +} + diff --git a/src/ServerTests/test/ServerComparison.TestSites/Properties/launchSettings.json b/src/ServerTests/test/ServerComparison.TestSites/Properties/launchSettings.json new file mode 100644 index 0000000000..7e255d2086 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39982/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNET_ENVIRONMENT": "HelloWorld" + } + }, + "web": { + "commandName": "web", + "environmentVariables": { + "ASPNET_ENVIRONMENT": "HelloWorld" + } + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.TestSites/ServerComparison.TestSites.csproj b/src/ServerTests/test/ServerComparison.TestSites/ServerComparison.TestSites.csproj new file mode 100644 index 0000000000..f3aa4898e5 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/ServerComparison.TestSites.csproj @@ -0,0 +1,28 @@ + + + + $(StandardTestTfms) + win7-x86;win7-x64;linux-x64;osx-x64 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ServerTests/test/ServerComparison.TestSites/StartupHelloWorld.cs b/src/ServerTests/test/ServerComparison.TestSites/StartupHelloWorld.cs new file mode 100644 index 0000000000..f92b8d45c7 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/StartupHelloWorld.cs @@ -0,0 +1,20 @@ +// 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.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace ServerComparison.TestSites +{ + public class StartupHelloWorld + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.Run(ctx => + { + return ctx.Response.WriteAsync("Hello World"); + }); + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.TestSites/StartupNtlmAuthentication.cs b/src/ServerTests/test/ServerComparison.TestSites/StartupNtlmAuthentication.cs new file mode 100644 index 0000000000..c4e837db58 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/StartupNtlmAuthentication.cs @@ -0,0 +1,62 @@ +// 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.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace ServerComparison.TestSites +{ + public class StartupNtlmAuthentication + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.Use(async (context, next) => + { + try + { + await next(); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + throw; + } + context.Response.Clear(); + context.Response.StatusCode = 500; + await context.Response.WriteAsync(ex.ToString()); + } + }); + + app.Use((context, next) => + { + if (context.Request.Path.Equals("/Anonymous")) + { + return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated); + } + + if (context.Request.Path.Equals("/Restricted")) + { + if (context.User.Identity.IsAuthenticated) + { + return context.Response.WriteAsync("Authenticated"); + } + else + { + return context.ChallengeAsync("Windows"); + } + } + + if (context.Request.Path.Equals("/Forbidden")) + { + return context.ForbidAsync("Windows"); + } + + return context.Response.WriteAsync("Hello World"); + }); + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.TestSites/StartupResponseCompression.cs b/src/ServerTests/test/ServerComparison.TestSites/StartupResponseCompression.cs new file mode 100644 index 0000000000..4f5398fe4c --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/StartupResponseCompression.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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ServerComparison.TestSites +{ + public class StartupResponseCompression + { + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + // NGinx's default min size is 20 bytes + var helloWorldBody = "Hello World;" + new string('a', 20); + + app.Map("/NoAppCompression", subApp => + { + subApp.Run(context => + { + context.Response.ContentType = "text/plain"; + context.Response.ContentLength = helloWorldBody.Length; + return context.Response.WriteAsync(helloWorldBody); + }); + }); + + app.Map("/AppCompression", subApp => + { + subApp.UseResponseCompression(); + subApp.Run(context => + { + context.Response.ContentType = "text/plain"; + context.Response.ContentLength = helloWorldBody.Length; + return context.Response.WriteAsync(helloWorldBody); + }); + }); + /* If we implement DisableResponseBuffering on IISMiddleware + app.Map("/NoBuffer", subApp => + { + subApp.UseResponseCompression(); + subApp.Run(context => + { + context.Features.Get().DisableResponseBuffering(); + context.Response.ContentType = "text/plain"; + context.Response.ContentLength = helloWorldBody.Length; + return context.Response.WriteAsync(helloWorldBody); + }); + }); + */ + app.Run(context => + { + context.Response.ContentType = "text/plain"; + string body; + if (context.Request.Path.Value == "/") + { + body = "Running"; + } + else + { + body = "Not Implemented: " + context.Request.Path; + } + + context.Response.ContentLength = body.Length; + return context.Response.WriteAsync(body); + }); + } + } +} \ No newline at end of file diff --git a/src/ServerTests/test/ServerComparison.TestSites/StartupResponses.cs b/src/ServerTests/test/ServerComparison.TestSites/StartupResponses.cs new file mode 100644 index 0000000000..3fc22ee091 --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/StartupResponses.cs @@ -0,0 +1,68 @@ +// 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.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace ServerComparison.TestSites +{ + public class StartupResponses + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.Map("/contentlength", subApp => + { + subApp.Run(context => + { + context.Response.ContentLength = 14; + return context.Response.WriteAsync("Content Length"); + }); + }); + + app.Map("/connectionclose", subApp => + { + subApp.Run(async context => + { + context.Response.Headers[HeaderNames.Connection] = "close"; + await context.Response.WriteAsync("Connnection Close"); + await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering + }); + }); + + app.Map("/chunked", subApp => + { + subApp.Run(async context => + { + await context.Response.WriteAsync("Chunked"); + await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering + }); + }); + + app.Map("/manuallychunked", subApp => + { + subApp.Run(context => + { + context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; + return context.Response.WriteAsync("10\r\nManually Chunked\r\n0\r\n\r\n"); + }); + }); + + app.Map("/manuallychunkedandclose", subApp => + { + subApp.Run(context => + { + context.Response.Headers[HeaderNames.Connection] = "close"; + context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; + return context.Response.WriteAsync("1A\r\nManually Chunked and Close\r\n0\r\n\r\n"); + }); + }); + + app.Run(context => + { + return context.Response.WriteAsync("Running"); + }); + } + } +} diff --git a/src/ServerTests/test/ServerComparison.TestSites/web.config b/src/ServerTests/test/ServerComparison.TestSites/web.config new file mode 100644 index 0000000000..3379e820ea --- /dev/null +++ b/src/ServerTests/test/ServerComparison.TestSites/web.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/ServerTests/version.props b/src/ServerTests/version.props new file mode 100644 index 0000000000..669c874829 --- /dev/null +++ b/src/ServerTests/version.props @@ -0,0 +1,12 @@ + + + 2.1.1 + rtm + $(VersionPrefix) + $(VersionPrefix)-$(VersionSuffix)-final + t000 + a- + $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) + $(VersionSuffix)-$(BuildNumber) + +