├── .appveyor.yml ├── .gitattributes ├── .gitignore ├── .vsts-pipelines ├── builds │ ├── ci-internal.yml │ └── ci-public.yml └── templates │ └── server-tests-steps.yml ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE.txt ├── NuGet.config ├── NuGetPackageVerifier.json ├── README.md ├── ServerTests.sln ├── build.cmd ├── build.sh ├── build ├── dependencies.props ├── repo.props └── sources.props ├── korebuild-lock.txt ├── korebuild.json ├── run.cmd ├── run.ps1 ├── run.sh ├── test ├── ServerComparison.FunctionalTests │ ├── HelloWorldTest.cs │ ├── Helpers.cs │ ├── NoCompression.conf │ ├── NtlmAuthenticationTest.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ResponseCompressionTests.cs │ ├── ResponseTests.cs │ ├── ServerComparison.FunctionalTests.csproj │ └── nginx.conf ├── ServerComparison.TestSites │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ServerComparison.TestSites.csproj │ ├── Startup.cs │ ├── StartupNtlmAuthentication.cs │ ├── StartupResponseCompression.cs │ └── StartupResponses.cs ├── aspnetcore_schema.xml ├── aspnetcore_schema_v2.xml └── update_schema.ps1 └── version.props /.appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf true 3 | branches: 4 | only: 5 | - master 6 | - /^release\/.*$/ 7 | - /^(.*\/)?ci-.*$/ 8 | environment: 9 | global: 10 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 11 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 12 | install: 13 | - ps: .\test\update_schema.ps1 14 | - net start w3svc 15 | build_script: 16 | - ps: .\run.ps1 default-build 17 | clone_depth: 1 18 | test: 'off' 19 | deploy: 'off' 20 | os: Visual Studio 2017 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | *.sh eol=lf 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | *.sln.ide/ 6 | _ReSharper.*/ 7 | packages/ 8 | artifacts/ 9 | PublishProfiles/ 10 | *.user 11 | *.suo 12 | *.cache 13 | *.docstates 14 | _ReSharper.* 15 | nuget.exe 16 | *net45.csproj 17 | *net451.csproj 18 | *k10.csproj 19 | *.psess 20 | *.vsp 21 | *.pidb 22 | *.userprefs 23 | *DS_Store 24 | *.ncrunchsolution 25 | *.*sdf 26 | *.ipch 27 | project.lock.json 28 | /.vs/ 29 | .testPublish/ 30 | .build/ 31 | global.json 32 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-internal.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | resources: 6 | repositories: 7 | - repository: buildtools 8 | type: git 9 | name: aspnet-BuildTools 10 | ref: refs/heads/master 11 | 12 | phases: 13 | - template: ../templates/server-tests-steps.yml 14 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-public.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | # See https://github.com/aspnet/BuildTools 6 | resources: 7 | repositories: 8 | - repository: buildtools 9 | type: github 10 | endpoint: DotNet-Bot GitHub Connection 11 | name: aspnet/BuildTools 12 | ref: refs/heads/master 13 | 14 | phases: 15 | - template: ../templates/server-tests-steps.yml 16 | -------------------------------------------------------------------------------- /.vsts-pipelines/templates/server-tests-steps.yml: -------------------------------------------------------------------------------- 1 | phases: 2 | - template: .vsts-pipelines/templates/phases/default-build.yml@buildtools 3 | parameters: 4 | agentOs: Windows 5 | beforeBuild: 6 | - powershell: "& ./test/update_schema.ps1" 7 | - powershell: Restart-Service w3svc 8 | 9 | - template: .vsts-pipelines/templates/phases/default-build.yml@buildtools 10 | parameters: 11 | agentOs: macOS 12 | beforeBuild: 13 | - script: brew install nginx 14 | 15 | - template: .vsts-pipelines/templates/phases/default-build.yml@buildtools 16 | parameters: 17 | agentOs: Linux 18 | beforeBuild: 19 | - script: apt-get update 20 | - script: apt-get install libpcre3 libpcre3-dev nginx 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Microsoft ASP.NET Core 12 | https://github.com/aspnet/servertests 13 | git 14 | $(MSBuildThisFileDirectory) 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MicrosoftNETCoreAppPackageVersion) 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) .NET Foundation and Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NuGetPackageVerifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "rules": [ 4 | "DefaultCompositeRule" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Server Tests [Archived] 2 | ======================= 3 | 4 | **This GitHub project has been archived.** Ongoing development on this project can be found in . 5 | 6 | This repo hosts [HttpSysServer](https://github.com/aspnet/HttpSysServer) and [Kestrel](https://github.com/aspnet/KestrelHttpServer) tests. 7 | 8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. 9 | -------------------------------------------------------------------------------- /ServerTests.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2036 4 | MinimumVisualStudioVersion = 15.0.26730.03 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{49AB8AAA-8160-48DF-A18B-78F51E54E02A}" 6 | ProjectSection(SolutionItems) = preProject 7 | .appveyor.yml = .appveyor.yml 8 | .travis.yml = .travis.yml 9 | Directory.Build.props = Directory.Build.props 10 | Directory.Build.targets = Directory.Build.targets 11 | NuGet.config = NuGet.config 12 | build\repo.props = build\repo.props 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FA91F388-F4AF-4850-9D68-D4D128E6B1A6}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerComparison.FunctionalTests", "test\ServerComparison.FunctionalTests\ServerComparison.FunctionalTests.csproj", "{A319ACCE-060B-4385-9534-9F2202F6180E}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerComparison.TestSites", "test\ServerComparison.TestSites\ServerComparison.TestSites.csproj", "{030225D8-4EE8-47E5-B692-2A96B3B51A38}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{55694E45-5EDE-46F8-80AA-797DE5F8C5C3}" 22 | ProjectSection(SolutionItems) = preProject 23 | build\dependencies.props = build\dependencies.props 24 | build\repo.props = build\repo.props 25 | build\sources.props = build\sources.props 26 | EndProjectSection 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {A319ACCE-060B-4385-9534-9F2202F6180E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {A319ACCE-060B-4385-9534-9F2202F6180E}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {A319ACCE-060B-4385-9534-9F2202F6180E}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {A319ACCE-060B-4385-9534-9F2202F6180E}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {030225D8-4EE8-47E5-B692-2A96B3B51A38}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {A319ACCE-060B-4385-9534-9F2202F6180E} = {FA91F388-F4AF-4850-9D68-D4D128E6B1A6} 48 | {030225D8-4EE8-47E5-B692-2A96B3B51A38} = {FA91F388-F4AF-4850-9D68-D4D128E6B1A6} 49 | EndGlobalSection 50 | GlobalSection(ExtensibilityGlobals) = postSolution 51 | SolutionGuid = {8A313020-8407-494F-81D7-7631580C5FCC} 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) 7 | chmod +x "$DIR/run.sh"; sync 8 | "$DIR/run.sh" default-build "$@" 9 | -------------------------------------------------------------------------------- /build/dependencies.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 4 | 5 | 6 | 3.0.0-build-20181114.5 7 | 3.0.0-alpha1-10742 8 | 3.0.0-alpha1-10742 9 | 3.0.0-alpha1-10742 10 | 3.0.0-alpha1-10742 11 | 3.0.0-alpha1-10742 12 | 3.0.0-alpha1-10742 13 | 3.0.0-alpha1-10742 14 | 3.0.0-alpha1-10742 15 | 3.0.0-alpha1-10742 16 | 3.0.0-preview-181113-11 17 | 3.0.0-preview-181113-11 18 | 3.0.0-preview-181113-11 19 | 3.0.0-preview-181113-11 20 | 3.0.0-preview-181113-11 21 | 3.0.0-preview1-26907-05 22 | 3.0.0-alpha1-10742 23 | 15.6.1 24 | 1.4.0 25 | 4.0.0 26 | 2.3.1 27 | 2.4.0 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build/repo.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Internal.AspNetCore.Universe.Lineup 7 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/sources.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(DotNetRestoreSources) 6 | 7 | $(RestoreSources); 8 | https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; 9 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; 10 | https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; 11 | 12 | 13 | $(RestoreSources); 14 | https://api.nuget.org/v3/index.json; 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /korebuild-lock.txt: -------------------------------------------------------------------------------- 1 | version:3.0.0-build-20181114.5 2 | commithash:880e9a204d4ee4a18dfd83c9fb05a192a28bca60 3 | -------------------------------------------------------------------------------- /korebuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 3 | "channel": "master" 4 | } 5 | -------------------------------------------------------------------------------- /run.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /run.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | #requires -version 4 3 | 4 | <# 5 | .SYNOPSIS 6 | Executes KoreBuild commands. 7 | 8 | .DESCRIPTION 9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. 10 | 11 | .PARAMETER Command 12 | The KoreBuild command to run. 13 | 14 | .PARAMETER Path 15 | The folder to build. Defaults to the folder containing this script. 16 | 17 | .PARAMETER Channel 18 | The channel of KoreBuild to download. Overrides the value from the config file. 19 | 20 | .PARAMETER DotNetHome 21 | The directory where .NET Core tools will be stored. 22 | 23 | .PARAMETER ToolsSource 24 | The base url where build tools can be downloaded. Overrides the value from the config file. 25 | 26 | .PARAMETER Update 27 | Updates KoreBuild to the latest version even if a lock file is present. 28 | 29 | .PARAMETER Reinstall 30 | Re-installs KoreBuild 31 | 32 | .PARAMETER ConfigFile 33 | The path to the configuration file that stores values. Defaults to korebuild.json. 34 | 35 | .PARAMETER ToolsSourceSuffix 36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. 37 | 38 | .PARAMETER CI 39 | Sets up CI specific settings and variables. 40 | 41 | .PARAMETER Arguments 42 | Arguments to be passed to the command 43 | 44 | .NOTES 45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. 46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. 47 | 48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set 49 | in the file are overridden by command line parameters. 50 | 51 | .EXAMPLE 52 | Example config file: 53 | ```json 54 | { 55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 56 | "channel": "master", 57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" 58 | } 59 | ``` 60 | #> 61 | [CmdletBinding(PositionalBinding = $false)] 62 | param( 63 | [Parameter(Mandatory = $true, Position = 0)] 64 | [string]$Command, 65 | [string]$Path = $PSScriptRoot, 66 | [Alias('c')] 67 | [string]$Channel, 68 | [Alias('d')] 69 | [string]$DotNetHome, 70 | [Alias('s')] 71 | [string]$ToolsSource, 72 | [Alias('u')] 73 | [switch]$Update, 74 | [switch]$Reinstall, 75 | [string]$ToolsSourceSuffix, 76 | [string]$ConfigFile = $null, 77 | [switch]$CI, 78 | [Parameter(ValueFromRemainingArguments = $true)] 79 | [string[]]$Arguments 80 | ) 81 | 82 | Set-StrictMode -Version 2 83 | $ErrorActionPreference = 'Stop' 84 | 85 | # 86 | # Functions 87 | # 88 | 89 | function Get-KoreBuild { 90 | 91 | $lockFile = Join-Path $Path 'korebuild-lock.txt' 92 | 93 | if (!(Test-Path $lockFile) -or $Update) { 94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix 95 | } 96 | 97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 98 | if (!$version) { 99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" 100 | } 101 | $version = $version.TrimStart('version:').Trim() 102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) 103 | 104 | if ($Reinstall -and (Test-Path $korebuildPath)) { 105 | Remove-Item -Force -Recurse $korebuildPath 106 | } 107 | 108 | if (!(Test-Path $korebuildPath)) { 109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" 110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null 111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" 112 | 113 | try { 114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" 115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix 116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { 117 | # Use built-in commands where possible as they are cross-plat compatible 118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath 119 | } 120 | else { 121 | # Fallback to old approach for old installations of PowerShell 122 | Add-Type -AssemblyName System.IO.Compression.FileSystem 123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) 124 | } 125 | } 126 | catch { 127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore 128 | throw 129 | } 130 | finally { 131 | Remove-Item $tmpfile -ErrorAction Ignore 132 | } 133 | } 134 | 135 | return $korebuildPath 136 | } 137 | 138 | function Join-Paths([string]$path, [string[]]$childPaths) { 139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ } 140 | return $path 141 | } 142 | 143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { 144 | if ($RemotePath -notlike 'http*') { 145 | Copy-Item $RemotePath $LocalPath 146 | return 147 | } 148 | 149 | $retries = 10 150 | while ($retries -gt 0) { 151 | $retries -= 1 152 | try { 153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath 154 | return 155 | } 156 | catch { 157 | Write-Verbose "Request failed. $retries retries remaining" 158 | } 159 | } 160 | 161 | Write-Error "Download failed: '$RemotePath'." 162 | } 163 | 164 | # 165 | # Main 166 | # 167 | 168 | # Load configuration or set defaults 169 | 170 | $Path = Resolve-Path $Path 171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } 172 | 173 | if (Test-Path $ConfigFile) { 174 | try { 175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json 176 | if ($config) { 177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } 178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} 179 | } 180 | } 181 | catch { 182 | Write-Host -ForegroundColor Red $Error[0] 183 | Write-Error "$ConfigFile contains invalid JSON." 184 | exit 1 185 | } 186 | } 187 | 188 | if (!$DotNetHome) { 189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` 190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` 191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` 192 | else { Join-Path $PSScriptRoot '.dotnet'} 193 | } 194 | 195 | if (!$Channel) { $Channel = 'master' } 196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } 197 | 198 | # Execute 199 | 200 | $korebuildPath = Get-KoreBuild 201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') 202 | 203 | try { 204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI 205 | Invoke-KoreBuildCommand $Command @Arguments 206 | } 207 | finally { 208 | Remove-Module 'KoreBuild' -ErrorAction Ignore 209 | } 210 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # 6 | # variables 7 | # 8 | 9 | RESET="\033[0m" 10 | RED="\033[0;31m" 11 | YELLOW="\033[0;33m" 12 | MAGENTA="\033[0;95m" 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" 15 | verbose=false 16 | update=false 17 | reinstall=false 18 | repo_path="$DIR" 19 | channel='' 20 | tools_source='' 21 | tools_source_suffix='' 22 | ci=false 23 | 24 | # 25 | # Functions 26 | # 27 | __usage() { 28 | echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" 29 | echo "" 30 | echo "Arguments:" 31 | echo " command The command to be run." 32 | echo " ... Arguments passed to the command. Variable number of arguments allowed." 33 | echo "" 34 | echo "Options:" 35 | echo " --verbose Show verbose output." 36 | echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." 37 | echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." 38 | echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." 39 | echo " --path The directory to build. Defaults to the directory containing the script." 40 | echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." 41 | echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." 42 | echo " -u|--update Update to the latest KoreBuild even if the lock file is present." 43 | echo " --reinstall Reinstall KoreBuild." 44 | echo " --ci Apply CI specific settings and environment variables." 45 | echo "" 46 | echo "Description:" 47 | echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." 48 | echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." 49 | 50 | if [[ "${1:-}" != '--no-exit' ]]; then 51 | exit 2 52 | fi 53 | } 54 | 55 | get_korebuild() { 56 | local version 57 | local lock_file="$repo_path/korebuild-lock.txt" 58 | if [ ! -f "$lock_file" ] || [ "$update" = true ]; then 59 | __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" 60 | fi 61 | version="$(grep 'version:*' -m 1 "$lock_file")" 62 | if [[ "$version" == '' ]]; then 63 | __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" 64 | return 1 65 | fi 66 | version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" 67 | local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" 68 | 69 | if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then 70 | rm -rf "$korebuild_path" 71 | fi 72 | 73 | { 74 | if [ ! -d "$korebuild_path" ]; then 75 | mkdir -p "$korebuild_path" 76 | local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" 77 | tmpfile="$(mktemp)" 78 | echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" 79 | if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then 80 | unzip -q -d "$korebuild_path" "$tmpfile" 81 | fi 82 | rm "$tmpfile" || true 83 | fi 84 | 85 | source "$korebuild_path/KoreBuild.sh" 86 | } || { 87 | if [ -d "$korebuild_path" ]; then 88 | echo "Cleaning up after failed installation" 89 | rm -rf "$korebuild_path" || true 90 | fi 91 | return 1 92 | } 93 | } 94 | 95 | __error() { 96 | echo -e "${RED}error: $*${RESET}" 1>&2 97 | } 98 | 99 | __warn() { 100 | echo -e "${YELLOW}warning: $*${RESET}" 101 | } 102 | 103 | __machine_has() { 104 | hash "$1" > /dev/null 2>&1 105 | return $? 106 | } 107 | 108 | __get_remote_file() { 109 | local remote_path=$1 110 | local local_path=$2 111 | local remote_path_suffix=$3 112 | 113 | if [[ "$remote_path" != 'http'* ]]; then 114 | cp "$remote_path" "$local_path" 115 | return 0 116 | fi 117 | 118 | local failed=false 119 | if __machine_has wget; then 120 | wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 121 | else 122 | failed=true 123 | fi 124 | 125 | if [ "$failed" = true ] && __machine_has curl; then 126 | failed=false 127 | curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 128 | fi 129 | 130 | if [ "$failed" = true ]; then 131 | __error "Download failed: $remote_path" 1>&2 132 | return 1 133 | fi 134 | } 135 | 136 | # 137 | # main 138 | # 139 | 140 | command="${1:-}" 141 | shift 142 | 143 | while [[ $# -gt 0 ]]; do 144 | case $1 in 145 | -\?|-h|--help) 146 | __usage --no-exit 147 | exit 0 148 | ;; 149 | -c|--channel|-Channel) 150 | shift 151 | channel="${1:-}" 152 | [ -z "$channel" ] && __usage 153 | ;; 154 | --config-file|-ConfigFile) 155 | shift 156 | config_file="${1:-}" 157 | [ -z "$config_file" ] && __usage 158 | if [ ! -f "$config_file" ]; then 159 | __error "Invalid value for --config-file. $config_file does not exist." 160 | exit 1 161 | fi 162 | ;; 163 | -d|--dotnet-home|-DotNetHome) 164 | shift 165 | DOTNET_HOME="${1:-}" 166 | [ -z "$DOTNET_HOME" ] && __usage 167 | ;; 168 | --path|-Path) 169 | shift 170 | repo_path="${1:-}" 171 | [ -z "$repo_path" ] && __usage 172 | ;; 173 | -s|--tools-source|-ToolsSource) 174 | shift 175 | tools_source="${1:-}" 176 | [ -z "$tools_source" ] && __usage 177 | ;; 178 | --tools-source-suffix|-ToolsSourceSuffix) 179 | shift 180 | tools_source_suffix="${1:-}" 181 | [ -z "$tools_source_suffix" ] && __usage 182 | ;; 183 | -u|--update|-Update) 184 | update=true 185 | ;; 186 | --reinstall|-[Rr]einstall) 187 | reinstall=true 188 | ;; 189 | --ci|-[Cc][Ii]) 190 | ci=true 191 | ;; 192 | --verbose|-Verbose) 193 | verbose=true 194 | ;; 195 | --) 196 | shift 197 | break 198 | ;; 199 | *) 200 | break 201 | ;; 202 | esac 203 | shift 204 | done 205 | 206 | if ! __machine_has unzip; then 207 | __error 'Missing required command: unzip' 208 | exit 1 209 | fi 210 | 211 | if ! __machine_has curl && ! __machine_has wget; then 212 | __error 'Missing required command. Either wget or curl is required.' 213 | exit 1 214 | fi 215 | 216 | [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" 217 | if [ -f "$config_file" ]; then 218 | if __machine_has jq ; then 219 | if jq '.' "$config_file" >/dev/null ; then 220 | config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" 221 | config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" 222 | else 223 | __error "$config_file contains invalid JSON." 224 | exit 1 225 | fi 226 | elif __machine_has python ; then 227 | if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 228 | config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 229 | config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 230 | else 231 | __error "$config_file contains invalid JSON." 232 | exit 1 233 | fi 234 | elif __machine_has python3 ; then 235 | if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 236 | config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 237 | config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 238 | else 239 | __error "$config_file contains invalid JSON." 240 | exit 1 241 | fi 242 | else 243 | __error 'Missing required command: jq or python. Could not parse the JSON file.' 244 | exit 1 245 | fi 246 | 247 | [ ! -z "${config_channel:-}" ] && channel="$config_channel" 248 | [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" 249 | fi 250 | 251 | [ -z "$channel" ] && channel='master' 252 | [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' 253 | 254 | get_korebuild 255 | set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" 256 | invoke_korebuild_command "$command" "$@" 257 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/HelloWorldTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Server.IntegrationTesting; 7 | using Microsoft.AspNetCore.Testing.xunit; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Logging.Testing; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | using Xunit.Sdk; 13 | 14 | namespace ServerComparison.FunctionalTests 15 | { 16 | public class HelloWorldTests : LoggedTest 17 | { 18 | public HelloWorldTests(ITestOutputHelper output) : base(output) 19 | { 20 | } 21 | 22 | public static TestMatrix TestVariants 23 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) 24 | .WithTfms(Tfm.NetCoreApp30) 25 | .WithAllApplicationTypes() 26 | .WithAllAncmVersions() 27 | .WithAllHostingModels() 28 | .WithAllArchitectures(); 29 | 30 | [ConditionalTheory] 31 | [MemberData(nameof(TestVariants))] 32 | public async Task HelloWorld(TestVariant variant) 33 | { 34 | var testName = $"HelloWorld_{variant.Server}_{variant.Tfm}_{variant.Architecture}_{variant.ApplicationType}"; 35 | using (StartLog(out var loggerFactory, 36 | variant.Server == ServerType.Nginx ? LogLevel.Trace : LogLevel.Debug, // https://github.com/aspnet/ServerTests/issues/144 37 | testName)) 38 | { 39 | var logger = loggerFactory.CreateLogger("HelloWorld"); 40 | 41 | var deploymentParameters = new DeploymentParameters(variant) 42 | { 43 | ApplicationPath = Helpers.GetApplicationPath() 44 | }; 45 | 46 | if (variant.Server == ServerType.Nginx) 47 | { 48 | deploymentParameters.ServerConfigTemplateContent = Helpers.GetNginxConfigContent("nginx.conf"); 49 | } 50 | 51 | using (var deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 52 | { 53 | var deploymentResult = await deployer.DeployAsync(); 54 | 55 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 56 | var response = await RetryHelper.RetryRequest(() => 57 | { 58 | return deploymentResult.HttpClient.GetAsync(string.Empty); 59 | }, logger, deploymentResult.HostShutdownToken); 60 | 61 | var responseText = await response.Content.ReadAsStringAsync(); 62 | try 63 | { 64 | if (variant.Architecture == RuntimeArchitecture.x64) 65 | { 66 | Assert.Equal("Hello World X64", responseText); 67 | } 68 | else 69 | { 70 | Assert.Equal("Hello World X86", responseText); 71 | } 72 | } 73 | catch (XunitException) 74 | { 75 | logger.LogWarning(response.ToString()); 76 | logger.LogWarning(responseText); 77 | throw; 78 | } 79 | 80 | // Make sure it was the right server. 81 | var serverHeader = response.Headers.Server.ToString(); 82 | switch (variant.Server) 83 | { 84 | case ServerType.HttpSys: 85 | Assert.Equal("Microsoft-HTTPAPI/2.0", serverHeader); 86 | break; 87 | case ServerType.Nginx: 88 | Assert.StartsWith("nginx/", serverHeader); 89 | break; 90 | case ServerType.Kestrel: 91 | Assert.Equal("Kestrel", serverHeader); 92 | break; 93 | case ServerType.IIS: 94 | case ServerType.IISExpress: 95 | if (variant.HostingModel == HostingModel.OutOfProcess) 96 | { 97 | Assert.Equal("Kestrel", serverHeader); 98 | } 99 | else 100 | { 101 | Assert.StartsWith("Microsoft-IIS/", serverHeader); 102 | } 103 | break; 104 | default: 105 | throw new NotImplementedException(variant.Server.ToString()); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/Helpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using Microsoft.AspNetCore.Server.IntegrationTesting; 7 | 8 | namespace ServerComparison.FunctionalTests 9 | { 10 | public class Helpers 11 | { 12 | public static string GetApplicationPath() 13 | { 14 | var applicationBasePath = AppContext.BaseDirectory; 15 | 16 | var directoryInfo = new DirectoryInfo(applicationBasePath); 17 | do 18 | { 19 | var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "ServerTests.sln")); 20 | if (solutionFileInfo.Exists) 21 | { 22 | return Path.GetFullPath(Path.Combine(directoryInfo.FullName, "test", "ServerComparison.TestSites")); 23 | } 24 | 25 | directoryInfo = directoryInfo.Parent; 26 | } 27 | while (directoryInfo.Parent != null); 28 | 29 | throw new Exception($"Solution root could not be found using {applicationBasePath}"); 30 | } 31 | 32 | public static string GetNginxConfigContent(string nginxConfig) 33 | { 34 | var applicationBasePath = AppContext.BaseDirectory; 35 | var content = File.ReadAllText(Path.Combine(applicationBasePath, nginxConfig)); 36 | return content; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/NoCompression.conf: -------------------------------------------------------------------------------- 1 | error_log [errorlog]; 2 | user [user]; 3 | worker_processes 4; 4 | pid [pidFile]; 5 | 6 | events { 7 | worker_connections 768; 8 | } 9 | 10 | http { 11 | sendfile on; 12 | tcp_nopush on; 13 | tcp_nodelay on; 14 | keepalive_timeout 10; 15 | types_hash_max_size 2048; 16 | 17 | default_type application/octet-stream; 18 | 19 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 20 | ssl_prefer_server_ciphers on; 21 | 22 | access_log [accesslog]; 23 | 24 | gzip off; 25 | 26 | server { 27 | listen [listenPort]; 28 | location / { 29 | proxy_pass [redirectUri]; 30 | proxy_http_version 1.1; 31 | proxy_set_header Upgrade $http_upgrade; 32 | proxy_set_header Host $host; 33 | proxy_cache_bypass $http_upgrade; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/NtlmAuthenticationTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Server.IntegrationTesting; 8 | using Microsoft.AspNetCore.Testing.xunit; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Logging.Testing; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | using Xunit.Sdk; 14 | 15 | namespace ServerComparison.FunctionalTests 16 | { 17 | public class NtlmAuthenticationTests : LoggedTest 18 | { 19 | public NtlmAuthenticationTests(ITestOutputHelper output) : base(output) 20 | { 21 | } 22 | 23 | public static TestMatrix TestVariants 24 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys) 25 | .WithTfms(Tfm.NetCoreApp30) 26 | .WithAllAncmVersions() 27 | .WithAllHostingModels(); 28 | 29 | [ConditionalTheory] 30 | [MemberData(nameof(TestVariants))] 31 | public async Task NtlmAuthentication(TestVariant variant) 32 | { 33 | var testName = $"NtlmAuthentication_{variant.Server}_{variant.Tfm}_{variant.Architecture}_{variant.ApplicationType}"; 34 | using (StartLog(out var loggerFactory, testName)) 35 | { 36 | var logger = loggerFactory.CreateLogger("NtlmAuthenticationTest"); 37 | 38 | var deploymentParameters = new DeploymentParameters(variant) 39 | { 40 | ApplicationPath = Helpers.GetApplicationPath(), 41 | EnvironmentName = "NtlmAuthentication", // Will pick the Start class named 'StartupNtlmAuthentication' 42 | }; 43 | 44 | using (var deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 45 | { 46 | var deploymentResult = await deployer.DeployAsync(); 47 | var httpClient = deploymentResult.HttpClient; 48 | 49 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 50 | var response = await RetryHelper.RetryRequest(() => 51 | { 52 | return httpClient.GetAsync(string.Empty); 53 | }, logger, deploymentResult.HostShutdownToken); 54 | 55 | var responseText = await response.Content.ReadAsStringAsync(); 56 | try 57 | { 58 | Assert.Equal("Hello World", responseText); 59 | 60 | logger.LogInformation("Testing /Anonymous"); 61 | response = await httpClient.GetAsync("/Anonymous"); 62 | responseText = await response.Content.ReadAsStringAsync(); 63 | Assert.Equal("Anonymous?True", responseText); 64 | 65 | logger.LogInformation("Testing /Restricted"); 66 | response = await httpClient.GetAsync("/Restricted"); 67 | responseText = await response.Content.ReadAsStringAsync(); 68 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); 69 | Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); 70 | Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); 71 | 72 | logger.LogInformation("Testing /Forbidden"); 73 | response = await httpClient.GetAsync("/Forbidden"); 74 | Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); 75 | 76 | logger.LogInformation("Enabling Default Credentials"); 77 | 78 | // Change the http client to one that uses default credentials 79 | httpClient = deploymentResult.CreateHttpClient(new HttpClientHandler() { UseDefaultCredentials = true }); 80 | 81 | logger.LogInformation("Testing /Restricted"); 82 | response = await httpClient.GetAsync("/Restricted"); 83 | responseText = await response.Content.ReadAsStringAsync(); 84 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 85 | Assert.Equal("Authenticated", responseText); 86 | 87 | logger.LogInformation("Testing /Forbidden"); 88 | response = await httpClient.GetAsync("/Forbidden"); 89 | Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); 90 | } 91 | catch (XunitException) 92 | { 93 | logger.LogWarning(response.ToString()); 94 | logger.LogWarning(responseText); 95 | throw; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/ResponseCompressionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Runtime.CompilerServices; 11 | using System.Threading.Tasks; 12 | using System.Xml.Linq; 13 | using Microsoft.AspNetCore.Server.IntegrationTesting; 14 | using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; 15 | using Microsoft.AspNetCore.Testing.xunit; 16 | using Microsoft.Extensions.Logging; 17 | using Microsoft.Extensions.Logging.Testing; 18 | using Microsoft.Net.Http.Headers; 19 | using Xunit; 20 | using Xunit.Abstractions; 21 | using Xunit.Sdk; 22 | 23 | namespace ServerComparison.FunctionalTests 24 | { 25 | public class ResponseCompressionTests : LoggedTest 26 | { 27 | // NGinx's default min size is 20 bytes 28 | private static readonly string HelloWorldBody = "Hello World;" + new string('a', 20); 29 | 30 | public ResponseCompressionTests(ITestOutputHelper output) : base(output) 31 | { 32 | } 33 | 34 | public static TestMatrix NoCompressionTestVariants 35 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) 36 | .WithTfms(Tfm.NetCoreApp30) 37 | .WithAllAncmVersions() 38 | .WithAllHostingModels(); 39 | 40 | [ConditionalTheory] 41 | [MemberData(nameof(NoCompressionTestVariants))] 42 | public Task ResponseCompression_NoCompression(TestVariant variant) 43 | { 44 | return ResponseCompression(variant, CheckNoCompressionAsync, hostCompression: false); 45 | } 46 | 47 | public static TestMatrix HostCompressionTestVariants 48 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Nginx) 49 | .WithTfms(Tfm.NetCoreApp30) 50 | .WithAllAncmVersions() 51 | .WithAllHostingModels(); 52 | 53 | [ConditionalTheory] 54 | [MemberData(nameof(HostCompressionTestVariants))] 55 | public Task ResponseCompression_HostCompression(TestVariant variant) 56 | { 57 | return ResponseCompression(variant, CheckHostCompressionAsync, hostCompression: true); 58 | } 59 | 60 | public static TestMatrix AppCompressionTestVariants 61 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.HttpSys) // No pass-through compression for nginx 62 | .WithTfms(Tfm.NetCoreApp30) 63 | .WithAllAncmVersions() 64 | .WithAllHostingModels(); 65 | 66 | [ConditionalTheory] 67 | [MemberData(nameof(AppCompressionTestVariants))] 68 | public Task ResponseCompression_AppCompression(TestVariant variant) 69 | { 70 | return ResponseCompression(variant, CheckAppCompressionAsync, hostCompression: false); 71 | } 72 | 73 | public static TestMatrix HostAndAppCompressionTestVariants 74 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) 75 | .WithTfms(Tfm.NetCoreApp30) 76 | .WithAllAncmVersions() 77 | .WithAllHostingModels(); 78 | 79 | [ConditionalTheory] 80 | [MemberData(nameof(HostAndAppCompressionTestVariants))] 81 | public Task ResponseCompression_AppAndHostCompression(TestVariant variant) 82 | { 83 | return ResponseCompression(variant, CheckAppCompressionAsync, hostCompression: true); 84 | } 85 | 86 | private async Task ResponseCompression(TestVariant variant, 87 | Func scenario, 88 | bool hostCompression, 89 | [CallerMemberName] string testName = null) 90 | { 91 | testName = $"{testName}_{variant.Server}_{variant.Tfm}_{variant.Architecture}_{variant.ApplicationType}"; 92 | using (StartLog(out var loggerFactory, 93 | variant.Server == ServerType.Nginx ? LogLevel.Trace : LogLevel.Debug, // https://github.com/aspnet/ServerTests/issues/144 94 | testName)) 95 | { 96 | var logger = loggerFactory.CreateLogger("ResponseCompression"); 97 | 98 | var deploymentParameters = new DeploymentParameters(variant) 99 | { 100 | ApplicationPath = Helpers.GetApplicationPath(), 101 | EnvironmentName = "ResponseCompression", 102 | }; 103 | 104 | if (variant.Server == ServerType.Nginx) 105 | { 106 | deploymentParameters.ServerConfigTemplateContent = hostCompression 107 | ? Helpers.GetNginxConfigContent("nginx.conf") 108 | : Helpers.GetNginxConfigContent("NoCompression.conf"); 109 | } 110 | else if (variant.Server == ServerType.IISExpress && !hostCompression) 111 | { 112 | var iisDeploymentParameters = new IISDeploymentParameters(deploymentParameters); 113 | iisDeploymentParameters.ServerConfigActionList.Add( 114 | (element, _) => { 115 | var compressionElement = element 116 | .RequiredElement("system.webServer") 117 | .RequiredElement("httpCompression"); 118 | 119 | compressionElement 120 | .RequiredElement("dynamicTypes") 121 | .Elements() 122 | .SkipLast(1) 123 | .Remove(); 124 | 125 | compressionElement 126 | .RequiredElement("staticTypes") 127 | .Elements() 128 | .SkipLast(1) 129 | .Remove(); 130 | // last element in both dynamicTypes and staticTypes disables compression 131 | // 132 | }); 133 | deploymentParameters = iisDeploymentParameters; 134 | } 135 | 136 | using (var deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 137 | { 138 | var deploymentResult = await deployer.DeployAsync(); 139 | var httpClientHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None }; 140 | Assert.True(httpClientHandler.SupportsAutomaticDecompression); 141 | var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); 142 | 143 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 144 | var response = await RetryHelper.RetryRequest(() => 145 | { 146 | return httpClient.GetAsync(string.Empty); 147 | }, logger, deploymentResult.HostShutdownToken); 148 | 149 | var responseText = await response.Content.ReadAsStringAsync(); 150 | try 151 | { 152 | Assert.Equal("Running", responseText); 153 | } 154 | catch (XunitException) 155 | { 156 | logger.LogWarning(response.ToString()); 157 | logger.LogWarning(responseText); 158 | throw; 159 | } 160 | 161 | await scenario(httpClient, logger); 162 | } 163 | } 164 | } 165 | 166 | private static async Task CheckNoCompressionAsync(HttpClient client, ILogger logger) 167 | { 168 | logger.LogInformation("Testing /NoAppCompression"); 169 | var request = new HttpRequestMessage(HttpMethod.Get, "NoAppCompression"); 170 | request.Headers.AcceptEncoding.ParseAdd("gzip,deflate"); 171 | var response = await client.SendAsync(request); 172 | var responseText = await response.Content.ReadAsStringAsync(); 173 | try 174 | { 175 | Assert.Equal(HelloWorldBody, responseText); 176 | Assert.Equal(HelloWorldBody.Length.ToString(), GetContentLength(response)); 177 | Assert.Equal(0, response.Content.Headers.ContentEncoding.Count); 178 | } 179 | catch (XunitException) 180 | { 181 | logger.LogWarning(response.ToString()); 182 | logger.LogWarning(responseText); 183 | throw; 184 | } 185 | } 186 | 187 | private static Task CheckHostCompressionAsync(HttpClient client, ILogger logger) 188 | { 189 | return CheckCompressionAsync(client, "NoAppCompression", logger); 190 | } 191 | 192 | private static Task CheckAppCompressionAsync(HttpClient client, ILogger logger) 193 | { 194 | return CheckCompressionAsync(client, "AppCompression", logger); 195 | } 196 | 197 | private static async Task CheckCompressionAsync(HttpClient client, string url, ILogger logger) 198 | { 199 | // Manage the compression manually because HttpClient removes the Content-Encoding header when decompressing. 200 | logger.LogInformation($"Testing /{url}"); 201 | var request = new HttpRequestMessage(HttpMethod.Get, url); 202 | request.Headers.AcceptEncoding.ParseAdd("gzip,deflate"); 203 | var response = await client.SendAsync(request); 204 | var responseText = await response.Content.ReadAsStringAsync(); 205 | try 206 | { 207 | responseText = await ReadCompressedAsStringAsync(response.Content); 208 | Assert.Equal(HelloWorldBody, responseText); 209 | Assert.Equal(1, response.Content.Headers.ContentEncoding.Count); 210 | Assert.Equal("gzip", response.Content.Headers.ContentEncoding.First()); 211 | } 212 | catch (XunitException) 213 | { 214 | logger.LogWarning(response.ToString()); 215 | logger.LogWarning(responseText); 216 | throw; 217 | } 218 | } 219 | 220 | private static string GetContentLength(HttpResponseMessage response) 221 | { 222 | // Don't use response.Content.Headers.ContentLength, it will dynamically calculate the value if it can. 223 | return response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out var values) ? values.FirstOrDefault() : null; 224 | } 225 | 226 | private static async Task ReadCompressedAsStringAsync(HttpContent content) 227 | { 228 | using (var stream = await content.ReadAsStreamAsync()) 229 | using (var compressStream = new GZipStream(stream, CompressionMode.Decompress)) 230 | using (var reader = new StreamReader(compressStream)) 231 | { 232 | return await reader.ReadToEndAsync(); 233 | } 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/ResponseTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Runtime.CompilerServices; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Server.IntegrationTesting; 11 | using Microsoft.AspNetCore.Testing.xunit; 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.Logging.Testing; 14 | using Microsoft.Net.Http.Headers; 15 | using Xunit; 16 | using Xunit.Abstractions; 17 | using Xunit.Sdk; 18 | 19 | namespace ServerComparison.FunctionalTests 20 | { 21 | public class ResponseTests : LoggedTest 22 | { 23 | public ResponseTests(ITestOutputHelper output) : base(output) 24 | { 25 | } 26 | 27 | public static TestMatrix TestVariants 28 | => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) 29 | .WithTfms(Tfm.NetCoreApp30) 30 | .WithAllAncmVersions() 31 | .WithAllHostingModels(); 32 | 33 | [ConditionalTheory] 34 | [MemberData(nameof(TestVariants))] 35 | public Task ResponseFormats_ContentLength(TestVariant variant) 36 | { 37 | return ResponseFormats(variant, CheckContentLengthAsync); 38 | } 39 | 40 | [ConditionalTheory] 41 | [MemberData(nameof(TestVariants))] 42 | public Task ResponseFormats_Chunked(TestVariant variant) 43 | { 44 | return ResponseFormats(variant, CheckChunkedAsync); 45 | } 46 | 47 | [ConditionalTheory] 48 | [MemberData(nameof(TestVariants))] 49 | public Task ResponseFormats_ManuallyChunk(TestVariant variant) 50 | { 51 | return ResponseFormats(variant, CheckManuallyChunkedAsync); 52 | } 53 | 54 | public static TestMatrix SelfhostTestVariants 55 | => TestMatrix.ForServers(ServerType.Kestrel, ServerType.HttpSys) 56 | .WithTfms(Tfm.NetCoreApp30); 57 | 58 | // Connection Close tests do not work through reverse proxies 59 | [ConditionalTheory] 60 | [MemberData(nameof(SelfhostTestVariants))] 61 | public Task ResponseFormats_Http10ConnectionClose(TestVariant variant) 62 | { 63 | return ResponseFormats(variant, CheckHttp10ConnectionCloseAsync); 64 | } 65 | 66 | [ConditionalTheory] 67 | [MemberData(nameof(SelfhostTestVariants))] 68 | public Task ResponseFormats_Http11ConnectionClose(TestVariant variant) 69 | { 70 | return ResponseFormats(variant, CheckHttp11ConnectionCloseAsync); 71 | } 72 | 73 | [ConditionalTheory] 74 | [MemberData(nameof(SelfhostTestVariants))] 75 | public Task ResponseFormats_ManuallyChunkAndClose(TestVariant variant) 76 | { 77 | return ResponseFormats(variant, CheckManuallyChunkedAndCloseAsync); 78 | } 79 | 80 | private async Task ResponseFormats(TestVariant variant, Func scenario, [CallerMemberName] string testName = null) 81 | { 82 | testName = $"{testName}_{variant.Server}_{variant.Tfm}_{variant.Architecture}_{variant.ApplicationType}"; 83 | using (StartLog(out var loggerFactory, 84 | variant.Server == ServerType.Nginx ? LogLevel.Trace : LogLevel.Debug, // https://github.com/aspnet/ServerTests/issues/144 85 | testName)) 86 | { 87 | var logger = loggerFactory.CreateLogger("ResponseFormats"); 88 | 89 | var deploymentParameters = new DeploymentParameters(variant) 90 | { 91 | ApplicationPath = Helpers.GetApplicationPath(), 92 | EnvironmentName = "Responses" 93 | }; 94 | 95 | if (variant.Server == ServerType.Nginx) 96 | { 97 | deploymentParameters.ServerConfigTemplateContent = Helpers.GetNginxConfigContent("nginx.conf"); 98 | } 99 | 100 | using (var deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 101 | { 102 | var deploymentResult = await deployer.DeployAsync(); 103 | 104 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 105 | var response = await RetryHelper.RetryRequest(() => 106 | { 107 | return deploymentResult.HttpClient.GetAsync(string.Empty); 108 | }, logger, deploymentResult.HostShutdownToken); 109 | 110 | var responseText = await response.Content.ReadAsStringAsync(); 111 | try 112 | { 113 | Assert.Equal("Running", responseText); 114 | } 115 | catch (XunitException) 116 | { 117 | logger.LogWarning(response.ToString()); 118 | logger.LogWarning(responseText); 119 | throw; 120 | } 121 | 122 | await scenario(deploymentResult.HttpClient, logger); 123 | } 124 | } 125 | } 126 | 127 | private static async Task CheckContentLengthAsync(HttpClient client, ILogger logger) 128 | { 129 | logger.LogInformation("Testing ContentLength"); 130 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, "contentlength") 131 | { 132 | Version = new Version(1, 1) 133 | }; 134 | 135 | var response = await client.SendAsync(requestMessage); 136 | var responseText = await response.Content.ReadAsStringAsync(); 137 | try 138 | { 139 | Assert.Equal("Content Length", responseText); 140 | Assert.Null(response.Headers.TransferEncodingChunked); 141 | Assert.Null(response.Headers.ConnectionClose); 142 | Assert.Equal("14", GetContentLength(response)); 143 | } 144 | catch (XunitException) 145 | { 146 | logger.LogWarning(response.ToString()); 147 | logger.LogWarning(responseText); 148 | throw; 149 | } 150 | } 151 | 152 | private static async Task CheckHttp11ConnectionCloseAsync(HttpClient client, ILogger logger) 153 | { 154 | logger.LogInformation("Testing Http11ConnectionClose"); 155 | var response = await client.GetAsync("connectionclose"); 156 | var responseText = await response.Content.ReadAsStringAsync(); 157 | try 158 | { 159 | Assert.Equal("Connnection Close", responseText); 160 | Assert.True(response.Headers.ConnectionClose, "/connectionclose, closed?"); 161 | Assert.True(response.Headers.TransferEncodingChunked); 162 | Assert.Null(GetContentLength(response)); 163 | } 164 | catch (XunitException) 165 | { 166 | logger.LogWarning(response.ToString()); 167 | logger.LogWarning(responseText); 168 | throw; 169 | } 170 | } 171 | 172 | private static async Task CheckHttp10ConnectionCloseAsync(HttpClient client, ILogger logger) 173 | { 174 | logger.LogInformation("Testing Http10ConnectionClose"); 175 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, "connectionclose") 176 | { 177 | Version = new Version(1, 0) 178 | }; 179 | 180 | var response = await client.SendAsync(requestMessage); 181 | var responseText = await response.Content.ReadAsStringAsync(); 182 | try 183 | { 184 | Assert.Equal("Connnection Close", responseText); 185 | Assert.True(response.Headers.ConnectionClose, "/connectionclose, closed?"); 186 | Assert.Null(response.Headers.TransferEncodingChunked); 187 | Assert.Null(GetContentLength(response)); 188 | } 189 | catch (XunitException) 190 | { 191 | logger.LogWarning(response.ToString()); 192 | logger.LogWarning(responseText); 193 | throw; 194 | } 195 | } 196 | 197 | private static async Task CheckChunkedAsync(HttpClient client, ILogger logger) 198 | { 199 | logger.LogInformation("Testing Chunked"); 200 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, "chunked") 201 | { 202 | Version = new Version(1, 1) 203 | }; 204 | 205 | var response = await client.SendAsync(requestMessage); 206 | var responseText = await response.Content.ReadAsStringAsync(); 207 | try 208 | { 209 | Assert.Equal("Chunked", responseText); 210 | Assert.True(response.Headers.TransferEncodingChunked, "/chunked, chunked?"); 211 | Assert.Null(response.Headers.ConnectionClose); 212 | Assert.Null(GetContentLength(response)); 213 | } 214 | catch (XunitException) 215 | { 216 | logger.LogWarning(response.ToString()); 217 | logger.LogWarning(responseText); 218 | throw; 219 | } 220 | } 221 | 222 | private static async Task CheckManuallyChunkedAsync(HttpClient client, ILogger logger) 223 | { 224 | logger.LogInformation("Testing ManuallyChunked"); 225 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, "manuallychunked") 226 | { 227 | Version = new Version(1, 1) 228 | }; 229 | 230 | var response = await client.SendAsync(requestMessage); 231 | var responseText = await response.Content.ReadAsStringAsync(); 232 | try 233 | { 234 | Assert.Equal("Manually Chunked", responseText); 235 | Assert.True(response.Headers.TransferEncodingChunked, "/manuallychunked, chunked?"); 236 | Assert.Null(response.Headers.ConnectionClose); 237 | Assert.Null(GetContentLength(response)); 238 | } 239 | catch (XunitException) 240 | { 241 | logger.LogWarning(response.ToString()); 242 | logger.LogWarning(responseText); 243 | throw; 244 | } 245 | } 246 | 247 | private static async Task CheckManuallyChunkedAndCloseAsync(HttpClient client, ILogger logger) 248 | { 249 | logger.LogInformation("Testing ManuallyChunkedAndClose"); 250 | var response = await client.GetAsync("manuallychunkedandclose"); 251 | var responseText = await response.Content.ReadAsStringAsync(); 252 | try 253 | { 254 | Assert.Equal("Manually Chunked and Close", responseText); 255 | Assert.True(response.Headers.TransferEncodingChunked, "/manuallychunkedandclose, chunked?"); 256 | Assert.True(response.Headers.ConnectionClose, "/manuallychunkedandclose, closed?"); 257 | Assert.Null(GetContentLength(response)); 258 | } 259 | catch (XunitException) 260 | { 261 | logger.LogWarning(response.ToString()); 262 | logger.LogWarning(responseText); 263 | throw; 264 | } 265 | } 266 | 267 | private static string GetContentLength(HttpResponseMessage response) 268 | { 269 | // Don't use response.Content.Headers.ContentLength, it will dynamically calculate the value if it can. 270 | IEnumerable values; 271 | return response.Content.Headers.TryGetValues(HeaderNames.ContentLength, out values) ? values.FirstOrDefault() : null; 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/ServerComparison.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/ServerComparison.FunctionalTests/nginx.conf: -------------------------------------------------------------------------------- 1 | error_log [errorlog]; 2 | worker_processes 4; 3 | pid [pidFile]; 4 | 5 | events { 6 | worker_connections 768; 7 | } 8 | 9 | http { 10 | sendfile on; 11 | tcp_nopush on; 12 | tcp_nodelay on; 13 | keepalive_timeout 10; 14 | types_hash_max_size 2048; 15 | 16 | default_type application/octet-stream; 17 | 18 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 19 | ssl_prefer_server_ciphers on; 20 | 21 | access_log [accesslog]; 22 | 23 | gzip on; 24 | gzip_types text/plain; 25 | gzip_disable "msie6"; 26 | 27 | server { 28 | listen [listenPort]; 29 | location / { 30 | proxy_pass [redirectUri]; 31 | proxy_http_version 1.1; 32 | proxy_set_header Upgrade $http_upgrade; 33 | proxy_set_header Host $host; 34 | proxy_cache_bypass $http_upgrade; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Server.HttpSys; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace ServerComparison.TestSites 11 | { 12 | public static class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | var config = new ConfigurationBuilder() 17 | .AddCommandLine(args) 18 | .Build(); 19 | 20 | var builder = new WebHostBuilder() 21 | .UseConfiguration(config) 22 | .ConfigureLogging((_, factory) => 23 | { 24 | factory.AddConsole(); 25 | factory.AddFilter("Console", level => level >= LogLevel.Warning); 26 | }) 27 | .UseStartup("ServerComparison.TestSites"); 28 | 29 | // Switch between Kestrel, IIS, and HttpSys for different tests. Default to Kestrel for normal app execution. 30 | if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.HttpSys", StringComparison.Ordinal)) 31 | { 32 | if (string.Equals(builder.GetSetting("environment") ?? 33 | Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), 34 | "NtlmAuthentication", System.StringComparison.Ordinal)) 35 | { 36 | // Set up NTLM authentication for HttpSys as follows. 37 | // For IIS and IISExpress use inetmgr to setup NTLM authentication on the application or 38 | // modify the applicationHost.config to enable NTLM. 39 | builder.UseHttpSys(options => 40 | { 41 | options.Authentication.AllowAnonymous = true; 42 | options.Authentication.Schemes = 43 | AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM; 44 | }); 45 | } 46 | else 47 | { 48 | builder.UseHttpSys(); 49 | } 50 | } 51 | else 52 | { 53 | // Check that we are not using IIS inproc before we add Kestrel. 54 | builder.UseKestrel(); 55 | } 56 | 57 | builder.UseIISIntegration(); 58 | builder.UseIIS(); 59 | 60 | var host = builder.Build(); 61 | 62 | host.Run(); 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:39982/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNET_ENVIRONMENT": "HelloWorld" 16 | } 17 | }, 18 | "web": { 19 | "commandName": "web", 20 | "environmentVariables": { 21 | "ASPNET_ENVIRONMENT": "HelloWorld" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/ServerComparison.TestSites.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | win7-x86;win7-x64;linux-x64;osx-x64 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Runtime.InteropServices; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace ServerComparison.TestSites 10 | { 11 | public class Startup 12 | { 13 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 14 | { 15 | app.Run(ctx => 16 | { 17 | return ctx.Response.WriteAsync("Hello World " + RuntimeInformation.ProcessArchitecture); 18 | }); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/StartupNtlmAuthentication.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace ServerComparison.TestSites 11 | { 12 | public class StartupNtlmAuthentication 13 | { 14 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 15 | { 16 | app.Use(async (context, next) => 17 | { 18 | try 19 | { 20 | await next(); 21 | } 22 | catch (Exception ex) 23 | { 24 | if (context.Response.HasStarted) 25 | { 26 | throw; 27 | } 28 | context.Response.Clear(); 29 | context.Response.StatusCode = 500; 30 | await context.Response.WriteAsync(ex.ToString()); 31 | } 32 | }); 33 | 34 | app.Use((context, next) => 35 | { 36 | if (context.Request.Path.Equals("/Anonymous")) 37 | { 38 | return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated); 39 | } 40 | 41 | if (context.Request.Path.Equals("/Restricted")) 42 | { 43 | if (context.User.Identity.IsAuthenticated) 44 | { 45 | return context.Response.WriteAsync("Authenticated"); 46 | } 47 | else 48 | { 49 | return context.ChallengeAsync("Windows"); 50 | } 51 | } 52 | 53 | if (context.Request.Path.Equals("/Forbidden")) 54 | { 55 | return context.ForbidAsync("Windows"); 56 | } 57 | 58 | return context.Response.WriteAsync("Hello World"); 59 | }); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/StartupResponseCompression.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Http.Features; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace ServerComparison.TestSites 11 | { 12 | public class StartupResponseCompression 13 | { 14 | public void ConfigureServices(IServiceCollection services) 15 | { 16 | services.AddResponseCompression(); 17 | } 18 | 19 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 20 | { 21 | // NGinx's default min size is 20 bytes 22 | var helloWorldBody = "Hello World;" + new string('a', 20); 23 | 24 | app.Map("/NoAppCompression", subApp => 25 | { 26 | subApp.Run(context => 27 | { 28 | context.Response.ContentType = "text/plain"; 29 | context.Response.ContentLength = helloWorldBody.Length; 30 | return context.Response.WriteAsync(helloWorldBody); 31 | }); 32 | }); 33 | 34 | app.Map("/AppCompression", subApp => 35 | { 36 | subApp.UseResponseCompression(); 37 | subApp.Run(context => 38 | { 39 | context.Response.ContentType = "text/plain"; 40 | context.Response.ContentLength = helloWorldBody.Length; 41 | return context.Response.WriteAsync(helloWorldBody); 42 | }); 43 | }); 44 | /* If we implement DisableResponseBuffering on IISMiddleware 45 | app.Map("/NoBuffer", subApp => 46 | { 47 | subApp.UseResponseCompression(); 48 | subApp.Run(context => 49 | { 50 | context.Features.Get().DisableResponseBuffering(); 51 | context.Response.ContentType = "text/plain"; 52 | context.Response.ContentLength = helloWorldBody.Length; 53 | return context.Response.WriteAsync(helloWorldBody); 54 | }); 55 | }); 56 | */ 57 | app.Run(context => 58 | { 59 | context.Response.ContentType = "text/plain"; 60 | string body; 61 | if (context.Request.Path.Value == "/") 62 | { 63 | body = "Running"; 64 | } 65 | else 66 | { 67 | body = "Not Implemented: " + context.Request.Path; 68 | } 69 | 70 | context.Response.ContentLength = body.Length; 71 | return context.Response.WriteAsync(body); 72 | }); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /test/ServerComparison.TestSites/StartupResponses.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Net.Http.Headers; 8 | 9 | namespace ServerComparison.TestSites 10 | { 11 | public class StartupResponses 12 | { 13 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 14 | { 15 | app.Map("/contentlength", subApp => 16 | { 17 | subApp.Run(context => 18 | { 19 | context.Response.ContentLength = 14; 20 | return context.Response.WriteAsync("Content Length"); 21 | }); 22 | }); 23 | 24 | app.Map("/connectionclose", subApp => 25 | { 26 | subApp.Run(async context => 27 | { 28 | context.Response.Headers[HeaderNames.Connection] = "close"; 29 | await context.Response.WriteAsync("Connnection Close"); 30 | await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering 31 | }); 32 | }); 33 | 34 | app.Map("/chunked", subApp => 35 | { 36 | subApp.Run(async context => 37 | { 38 | await context.Response.WriteAsync("Chunked"); 39 | await context.Response.Body.FlushAsync(); // Bypass IIS write-behind buffering 40 | }); 41 | }); 42 | 43 | app.Map("/manuallychunked", subApp => 44 | { 45 | subApp.Run(context => 46 | { 47 | context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; 48 | return context.Response.WriteAsync("10\r\nManually Chunked\r\n0\r\n\r\n"); 49 | }); 50 | }); 51 | 52 | app.Map("/manuallychunkedandclose", subApp => 53 | { 54 | subApp.Run(context => 55 | { 56 | context.Response.Headers[HeaderNames.Connection] = "close"; 57 | context.Response.Headers[HeaderNames.TransferEncoding] = "chunked"; 58 | return context.Response.WriteAsync("1A\r\nManually Chunked and Close\r\n0\r\n\r\n"); 59 | }); 60 | }); 61 | 62 | app.Run(context => 63 | { 64 | return context.Response.WriteAsync("Running"); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/aspnetcore_schema.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/aspnetcore_schema_v2.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/update_schema.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | Updates aspnetcore_schema.xml to the latest version. 4 | Updates aspnetcore_schema_v2.xml to the latest version. 5 | Requires admin privileges. 6 | #> 7 | [cmdletbinding(SupportsShouldProcess = $true)] 8 | param() 9 | 10 | $ErrorActionPreference = 'Stop' 11 | Set-StrictMode -Version 1 12 | 13 | $ancmSchemaFiles = @( 14 | "aspnetcore_schema.xml", 15 | "aspnetcore_schema_v2.xml" 16 | ) 17 | 18 | $ancmSchemaFileLocations = @( 19 | @(Resolve-Path "$PSScriptRoot\aspnetcore_schema.xml"), 20 | @(Resolve-Path "$PSScriptRoot\aspnetcore_schema_v2.xml") 21 | ) 22 | 23 | [bool]$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 24 | 25 | if (-not $isAdmin -and -not $WhatIfPreference) { 26 | if ($PSCmdlet.ShouldContinue("Continue as an admin?", "This script needs admin privileges to update IIS Express and IIS.")) { 27 | $thisFile = Join-Path $PSScriptRoot $MyInvocation.MyCommand.Name 28 | 29 | Start-Process ` 30 | -Verb runas ` 31 | -FilePath "powershell.exe" ` 32 | -ArgumentList $thisFile ` 33 | -Wait ` 34 | | Out-Null 35 | 36 | if (-not $?) { 37 | throw 'Update failed' 38 | } 39 | exit 40 | } 41 | else { 42 | throw 'Requires admin privileges' 43 | } 44 | } 45 | 46 | for ($i=0; $i -lt $ancmSchemaFiles.Length; $i++) 47 | { 48 | $schemaFile = $ancmSchemaFiles[$i] 49 | $schemaSource = $ancmSchemaFileLocations[$i] 50 | 51 | $destinations = @( 52 | "${env:ProgramFiles(x86)}\IIS Express\config\schema\", 53 | "${env:ProgramFiles}\IIS Express\config\schema\", 54 | "${env:windir}\system32\inetsrv\config\schema\" 55 | ) 56 | 57 | foreach ($destPath in $destinations) { 58 | $dest = "$destPath\${schemaFile}"; 59 | 60 | if (!(Test-Path $destPath)) 61 | { 62 | Write-Host "$destPath doesn't exist" 63 | continue; 64 | } 65 | 66 | if ($PSCmdlet.ShouldProcess($dest, "Replace file")) { 67 | Write-Host "Updated $dest" 68 | Move-Item $dest "${dest}.bak" -ErrorAction Ignore 69 | Copy-Item $schemaSource $dest 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 3.0.0 4 | alpha1 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix)-final 7 | t000 8 | a- 9 | $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) 10 | $(VersionSuffix)-$(BuildNumber) 11 | 12 | 13 | --------------------------------------------------------------------------------