diff --git a/src/IISIntegration/.gitignore b/src/IISIntegration/.gitignore index f86f68b748..c18e14397c 100644 --- a/src/IISIntegration/.gitignore +++ b/src/IISIntegration/.gitignore @@ -7,6 +7,7 @@ _ReSharper.*/ packages/ artifacts/ PublishProfiles/ +BenchmarkDotNet.Artifacts/ *.user *.suo *.cache @@ -36,6 +37,7 @@ project.lock.json *.tlog *.CppClean.log *msbuild.log +gtest.log src/*/*/Debug/ src/*/*/x64/Debug/ @@ -48,18 +50,18 @@ x64/ *.pdb *.lib *.idb +*.TMP src/*/AspNetCore/aspnetcoremodule.h src/*/AspNetCore/aspnetcore_msg.h src/*/AspNetCore/aspnetcore_msg.rc src/*/*/version.h -src/*/RequestHandler/version.h +src/*/InProcessRequestHandler/version.h +src/*/OutOfProcessRequestHandler/version.h src/*/CommonLib/aspnetcore_msg.h src/*/CommonLib/aspnetcore_msg.rc test/*/Debug test/*/Release -test/gtest-1.8.0/msvc/Debug -test/gtest-1.8.0/msvc/Release .build *.VC.*db diff --git a/src/IISIntegration/Directory.Build.props b/src/IISIntegration/Directory.Build.props index 3bb2bcbca8..45a38d3e61 100644 --- a/src/IISIntegration/Directory.Build.props +++ b/src/IISIntegration/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -14,10 +14,9 @@ $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true - true true - - false + + false diff --git a/src/IISIntegration/Directory.Build.targets b/src/IISIntegration/Directory.Build.targets index 53b3f6e1da..73b97f2807 100644 --- a/src/IISIntegration/Directory.Build.targets +++ b/src/IISIntegration/Directory.Build.targets @@ -1,7 +1,9 @@ - + - $(MicrosoftNETCoreApp20PackageVersion) $(MicrosoftNETCoreApp21PackageVersion) + $(MicrosoftNETCoreApp22PackageVersion) $(NETStandardLibrary20PackageVersion) + + 99.9 diff --git a/src/IISIntegration/IISIntegration.NoV1.sln b/src/IISIntegration/IISIntegration.NoV1.sln new file mode 100644 index 0000000000..f7eb135f73 --- /dev/null +++ b/src/IISIntegration/IISIntegration.NoV1.sln @@ -0,0 +1,463 @@ + +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}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0EF45656-B25D-40D8-959C-726EAF185E60}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + NuGet.Config = NuGet.Config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EF30B533-D715-421A-92B7-92FEF460AC9C}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISSample", "samples\IISSample\IISSample.csproj", "{E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IISIntegration", "src\Microsoft.AspNetCore.Server.IISIntegration\Microsoft.AspNetCore.Server.IISIntegration.csproj", "{8B3446E8-E6A8-4591-AA63-A95837C6E97C}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IISIntegration.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj", "{4106DB10-E09F-480E-9CE6-B39235512EE6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7E80C58E-9CC8-450C-8A8D-94FC76428150}" + ProjectSection(SolutionItems) = preProject + build\applicationhost.config = build\applicationhost.config + build\applicationhost.iis.config = build\applicationhost.iis.config + build\Build.Settings = build\Build.Settings + build\Config.Definitions.Props = build\Config.Definitions.Props + build\dependencies.props = build\dependencies.props + build\functional-test-assets.targets = build\functional-test-assets.targets + build\Key.snk = build\Key.snk + build\launchSettings.json = build\launchSettings.json + build\native.targets = build\native.targets + build\repo.props = build\repo.props + build\repo.targets = build\repo.targets + build\sources.props = build\sources.props + build\testsite.props = build\testsite.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISExpress.FunctionalTests", "test\IISExpress.FunctionalTests\IISExpress.FunctionalTests.csproj", "{4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}" + ProjectSection(ProjectDependencies) = postProject + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {7F87406C-A3C8-4139-A68D-E4C344294A67} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeIISSample", "samples\NativeIISSample\NativeIISSample.csproj", "{9BC4AFCB-325D-4C81-8228-8CF301CE2F97}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessWebSite", "test\WebSites\InProcessWebSite\InProcessWebSite.csproj", "{679FA2A2-898B-4320-884E-C2D294A97CE1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj", "{46A8612B-418B-4D70-B3A7-A21DD0627473}" + ProjectSection(ProjectDependencies) = postProject + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {55494E58-E061-4C4C-A0A8-837008E72F85} + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {7F87406C-A3C8-4139-A68D-E4C344294A67} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {D57EA297-6DC2-4BC0-8C91-334863327863} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StressTestWebSite", "test\WebSites\StressTestWebSite\StressTestWebSite.csproj", "{13FD8F12-FFBE-4D01-B4AC-444F2994B04F}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTasks", "test\TestTasks\TestTasks.csproj", "{064D860B-4D7C-4B1D-918F-E020F1B99E2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{744ACDC6-F6A0-4FF9-9421-F25C5F2DC520}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLibTests", "test\CommonLibTests\CommonLibTests.vcxproj", "{1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCoreModuleV2", "AspNetCoreModuleV2", "{06CA2C2B-83B0-4D83-905A-E0C74790009E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCoreModuleV2\AspNetCore\AspNetCore.vcxproj", "{EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\AspNetCoreModuleV2\CommonLib\CommonLib.vcxproj", "{55494E58-E061-4C4C-A0A8-837008E72F85}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\AspNetCoreModuleV2\IISLib\IISLib.vcxproj", "{09D9D1D6-2951-4E14-BC35-76A23CF9391A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutOfProcessWebSite", "test\WebSites\OutOfProcessWebSite\OutOfProcessWebSite.csproj", "{42E60F88-E23F-417A-8143-0CCEC05E1D02}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{622D35C9-627B-466E-8D15-752968CC79AF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Performance", "benchmarks\IIS.Performance\IIS.Performance.csproj", "{48F46909-E76A-4788-BCE1-E543C0E140FE}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InProcessRequestHandler", "src\AspNetCoreModuleV2\InProcessRequestHandler\InProcessRequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" + ProjectSection(ProjectDependencies) = postProject + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {09D9D1D6-2951-4E14-BC35-76A23CF9391A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OutOfProcessRequestHandler", "src\AspNetCoreModuleV2\OutOfProcessRequestHandler\OutOfProcessRequestHandler.vcxproj", "{7F87406C-A3C8-4139-A68D-E4C344294A67}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "test\gtest\gtest.vcxproj", "{CAC1267B-8778-4257-AAC6-CAF481723B01}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandlerLib", "src\AspNetCoreModuleV2\RequestHandlerLib\RequestHandlerLib.vcxproj", "{1533E271-F61B-441B-8B74-59FB61DF0552}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.FunctionalTests", "test\IIS.FunctionalTests\IIS.FunctionalTests.csproj", "{D182103F-8405-4647-B158-C36F598657EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IntegrationTesting.IIS", "src\Microsoft.AspNetCore.Server.IntegrationTesting.IIS\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", "{34135ED7-313D-4E68-860C-D6B51AA28523}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Tests", "test\IIS.Tests\IIS.Tests.csproj", "{C0310D84-BC2F-4B2E-870E-D35044DB3E3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Tests", "test\Common.Tests\Common.Tests.csproj", "{D17B7B35-5361-4A50-B499-E03E5C3CC095}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.BackwardsCompatibility.FunctionalTests", "test\IIS.BackwardsCompatibility.FunctionalTests\IIS.BackwardsCompatibility.FunctionalTests.csproj", "{582B07BC-73F4-4689-8557-B039298BD82C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.ForwardsCompatibility.FunctionalTests", "test\IIS.ForwardsCompatibility.FunctionalTests\IIS.ForwardsCompatibility.FunctionalTests.csproj", "{D1EA5D99-28FD-4197-81DE-17098846B38B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessForwardsCompatWebSite", "test\WebSites\InProcessForwardsCompatWebSite\InProcessWebSite.csproj", "{BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x64.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x64.Build.0 = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x86.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x86.Build.0 = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|Any CPU.Build.0 = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|x64.ActiveCfg = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|x64.Build.0 = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|x86.ActiveCfg = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|x86.Build.0 = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x64.Build.0 = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x86.Build.0 = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|Any CPU.Build.0 = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|x64.ActiveCfg = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|x64.Build.0 = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|x86.ActiveCfg = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|x86.Build.0 = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x64.Build.0 = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x86.Build.0 = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.Build.0 = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x64.ActiveCfg = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x64.Build.0 = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x86.ActiveCfg = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x86.Build.0 = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x64.Build.0 = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x86.Build.0 = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|Any CPU.Build.0 = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|x64.ActiveCfg = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|x64.Build.0 = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|x86.ActiveCfg = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|x86.Build.0 = Release|Any CPU + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|Any CPU.ActiveCfg = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|Any CPU.Build.0 = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x64.ActiveCfg = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x64.Build.0 = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x86.ActiveCfg = Debug|x86 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x86.Build.0 = Debug|x86 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|Any CPU.ActiveCfg = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|Any CPU.Build.0 = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x64.ActiveCfg = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x64.Build.0 = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x86.ActiveCfg = Release|x86 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x86.Build.0 = Release|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|Any CPU.ActiveCfg = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|Any CPU.Build.0 = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x64.ActiveCfg = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x64.Build.0 = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x86.ActiveCfg = Debug|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x86.Build.0 = Debug|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|Any CPU.ActiveCfg = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|Any CPU.Build.0 = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.ActiveCfg = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.Build.0 = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.ActiveCfg = Release|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.Build.0 = Release|x86 + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x64.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x64.Build.0 = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x86.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x86.Build.0 = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|Any CPU.Build.0 = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x64.ActiveCfg = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x64.Build.0 = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x86.ActiveCfg = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x86.Build.0 = Release|Any CPU + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.Build.0 = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.ActiveCfg = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.Build.0 = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.ActiveCfg = Debug|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.Build.0 = Debug|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.ActiveCfg = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.Build.0 = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.ActiveCfg = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.Build.0 = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x86.ActiveCfg = Release|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x86.Build.0 = Release|x86 + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x64.Build.0 = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x86.Build.0 = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|Any CPU.Build.0 = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x64.ActiveCfg = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x64.Build.0 = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x86.ActiveCfg = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x86.Build.0 = Release|Any CPU + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.ActiveCfg = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.Build.0 = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.Build.0 = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|Any CPU.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.ActiveCfg = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.Build.0 = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.Build.0 = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x64.ActiveCfg = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x64.Build.0 = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.Build.0 = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|Any CPU.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.ActiveCfg = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.Build.0 = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x86.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x86.Build.0 = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.ActiveCfg = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.Build.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.Build.0 = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.ActiveCfg = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.Build.0 = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x86.Build.0 = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x64.ActiveCfg = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x64.Build.0 = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.Build.0 = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|Any CPU.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.ActiveCfg = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.Build.0 = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.Build.0 = Release|Win32 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|Any CPU.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|Any CPU.Build.0 = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x64.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x64.Build.0 = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x86.ActiveCfg = Debug|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x86.Build.0 = Debug|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|Any CPU.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|Any CPU.Build.0 = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x64.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x64.Build.0 = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x86.ActiveCfg = Release|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x86.Build.0 = Release|x86 + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x64.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x64.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x86.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x86.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|Any CPU.Build.0 = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x64.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x64.Build.0 = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x86.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x86.Build.0 = Release|Any CPU + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.ActiveCfg = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.Build.0 = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.Build.0 = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Any CPU.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Build.0 = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.ActiveCfg = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.Build.0 = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.Build.0 = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|Any CPU.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.ActiveCfg = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.Build.0 = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.Build.0 = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.ActiveCfg = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.Build.0 = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.Build.0 = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|Any CPU.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.ActiveCfg = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.Build.0 = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.Build.0 = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.ActiveCfg = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.Build.0 = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.Build.0 = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|Any CPU.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.ActiveCfg = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.Build.0 = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.Build.0 = Release|Win32 + {D182103F-8405-4647-B158-C36F598657EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Debug|x64.Build.0 = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Debug|x86.Build.0 = Debug|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|Any CPU.Build.0 = Release|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|x64.ActiveCfg = Release|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|x64.Build.0 = Release|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|x86.ActiveCfg = Release|Any CPU + {D182103F-8405-4647-B158-C36F598657EF}.Release|x86.Build.0 = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|x64.ActiveCfg = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|x64.Build.0 = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|x86.ActiveCfg = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Debug|x86.Build.0 = Debug|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|Any CPU.Build.0 = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x64.ActiveCfg = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x64.Build.0 = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x86.ActiveCfg = Release|Any CPU + {34135ED7-313D-4E68-860C-D6B51AA28523}.Release|x86.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x64.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Debug|x86.Build.0 = Debug|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|Any CPU.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x64.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x64.Build.0 = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x86.ActiveCfg = Release|Any CPU + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E}.Release|x86.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x64.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x64.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x86.ActiveCfg = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Debug|x86.Build.0 = Debug|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|Any CPU.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x64.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x64.Build.0 = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x86.ActiveCfg = Release|Any CPU + {D17B7B35-5361-4A50-B499-E03E5C3CC095}.Release|x86.Build.0 = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|x64.ActiveCfg = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|x64.Build.0 = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|x86.ActiveCfg = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Debug|x86.Build.0 = Debug|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|Any CPU.Build.0 = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|x64.ActiveCfg = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|x64.Build.0 = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|x86.ActiveCfg = Release|Any CPU + {582B07BC-73F4-4689-8557-B039298BD82C}.Release|x86.Build.0 = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|x64.Build.0 = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Debug|x86.Build.0 = Debug|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|Any CPU.Build.0 = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|x64.ActiveCfg = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|x64.Build.0 = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|x86.ActiveCfg = Release|Any CPU + {D1EA5D99-28FD-4197-81DE-17098846B38B}.Release|x86.Build.0 = Release|Any CPU + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Debug|Any CPU.ActiveCfg = Debug|x86 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Debug|x64.ActiveCfg = Debug|x64 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Debug|x64.Build.0 = Debug|x64 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Debug|x86.ActiveCfg = Debug|x86 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Debug|x86.Build.0 = Debug|x86 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Release|Any CPU.ActiveCfg = Release|x86 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Release|x64.ActiveCfg = Release|x64 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Release|x64.Build.0 = Release|x64 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Release|x86.ActiveCfg = Release|x86 + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} + {8B3446E8-E6A8-4591-AA63-A95837C6E97C} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {4106DB10-E09F-480E-9CE6-B39235512EE6} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} + {679FA2A2-898B-4320-884E-C2D294A97CE1} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {064D860B-4D7C-4B1D-918F-E020F1B99E2A} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {06CA2C2B-83B0-4D83-905A-E0C74790009E} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {42E60F88-E23F-417A-8143-0CCEC05E1D02} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {48F46909-E76A-4788-BCE1-E543C0E140FE} = {622D35C9-627B-466E-8D15-752968CC79AF} + {D57EA297-6DC2-4BC0-8C91-334863327863} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {CAC1267B-8778-4257-AAC6-CAF481723B01} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {1533E271-F61B-441B-8B74-59FB61DF0552} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {D182103F-8405-4647-B158-C36F598657EF} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {34135ED7-313D-4E68-860C-D6B51AA28523} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {C0310D84-BC2F-4B2E-870E-D35044DB3E3E} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {D17B7B35-5361-4A50-B499-E03E5C3CC095} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {582B07BC-73F4-4689-8557-B039298BD82C} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {D1EA5D99-28FD-4197-81DE-17098846B38B} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {BBBC85B2-5D7A-4D09-90B1-8DBCC9059493} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} + EndGlobalSection +EndGlobal diff --git a/src/IISIntegration/IISIntegration.sln b/src/IISIntegration/IISIntegration.sln index 50d11124bc..440001a8ac 100644 --- a/src/IISIntegration/IISIntegration.sln +++ b/src/IISIntegration/IISIntegration.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E96 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0EF45656-B25D-40D8-959C-726EAF185E60}" ProjectSection(SolutionItems) = preProject + .appveyor.yml = .appveyor.yml .editorconfig = .editorconfig Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets @@ -26,53 +27,57 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISSample", "samples\IISSample\IISSample.csproj", "{E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IISIntegration", "src\Microsoft.AspNetCore.Server.IISIntegration\Microsoft.AspNetCore.Server.IISIntegration.csproj", "{8B3446E8-E6A8-4591-AA63-A95837C6E97C}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IISIntegration.Tests", "test\Microsoft.AspNetCore.Server.IISIntegration.Tests\Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj", "{4106DB10-E09F-480E-9CE6-B39235512EE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutOfProcessWebSite", "test\WebSites\OutOfProcessWebSite\OutOfProcessWebSite.csproj", "{F54715C3-88D8-49E3-A291-C13570FE81FC}" - ProjectSection(ProjectDependencies) = postProject - {D57EA297-6DC2-4BC0-8C91-334863327863} = {D57EA297-6DC2-4BC0-8C91-334863327863} - {439824F9-1455-4CC4-BD79-B44FA0A16552} = {439824F9-1455-4CC4-BD79-B44FA0A16552} - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7E80C58E-9CC8-450C-8A8D-94FC76428150}" ProjectSection(SolutionItems) = preProject build\applicationhost.config = build\applicationhost.config build\applicationhost.iis.config = build\applicationhost.iis.config + build\build.msbuild = build\build.msbuild + build\Build.Settings = build\Build.Settings + build\Config.Definitions.Props = build\Config.Definitions.Props build\dependencies.props = build\dependencies.props build\Key.snk = build\Key.snk + build\launchSettings.json = build\launchSettings.json build\native.targets = build\native.targets build\repo.props = build\repo.props + build\repo.targets = build\repo.targets + build\sources.props = build\sources.props build\testsite.props = build\testsite.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISIntegration.FunctionalTests", "test\IISIntegration.FunctionalTests\IISIntegration.FunctionalTests.csproj", "{4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISExpress.FunctionalTests", "test\IISExpress.FunctionalTests\IISExpress.FunctionalTests.csproj", "{4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}" + ProjectSection(ProjectDependencies) = postProject + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {7F87406C-A3C8-4139-A68D-E4C344294A67} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeIISSample", "samples\NativeIISSample\NativeIISSample.csproj", "{9BC4AFCB-325D-4C81-8228-8CF301CE2F97}" - ProjectSection(ProjectDependencies) = postProject - {D57EA297-6DC2-4BC0-8C91-334863327863} = {D57EA297-6DC2-4BC0-8C91-334863327863} - {439824F9-1455-4CC4-BD79-B44FA0A16552} = {439824F9-1455-4CC4-BD79-B44FA0A16552} - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessWebSite", "test\WebSites\InProcessWebSite\InProcessWebSite.csproj", "{679FA2A2-898B-4320-884E-C2D294A97CE1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj", "{46A8612B-418B-4D70-B3A7-A21DD0627473}" ProjectSection(ProjectDependencies) = postProject + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} + {55494E58-E061-4C4C-A0A8-837008E72F85} = {55494E58-E061-4C4C-A0A8-837008E72F85} + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {7F87406C-A3C8-4139-A68D-E4C344294A67} {D57EA297-6DC2-4BC0-8C91-334863327863} = {D57EA297-6DC2-4BC0-8C91-334863327863} {439824F9-1455-4CC4-BD79-B44FA0A16552} = {439824F9-1455-4CC4-BD79-B44FA0A16552} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj", "{46A8612B-418B-4D70-B3A7-A21DD0627473}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StressTestWebSite", "test\WebSites\StressTestWebSite\StressTestWebSite.csproj", "{13FD8F12-FFBE-4D01-B4AC-444F2994B04F}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTasks", "test\TestTasks\TestTasks.csproj", "{064D860B-4D7C-4B1D-918F-E020F1B99E2A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{744ACDC6-F6A0-4FF9-9421-F25C5F2DC520}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OverriddenServerWebSite", "test\WebSites\OverriddenServerWebSite\OverriddenServerWebSite.csproj", "{FC2A97F8-A749-4C04-97D1-97500066A820}" - ProjectSection(ProjectDependencies) = postProject - {D57EA297-6DC2-4BC0-8C91-334863327863} = {D57EA297-6DC2-4BC0-8C91-334863327863} - {439824F9-1455-4CC4-BD79-B44FA0A16552} = {439824F9-1455-4CC4-BD79-B44FA0A16552} - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLibTests", "test\CommonLibTests\CommonLibTests.vcxproj", "{1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCoreModuleV1", "AspNetCoreModuleV1", "{16E521CE-77F1-4B1C-A183-520A41C4F372}" EndProject @@ -88,13 +93,51 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "src\AspNetCore EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\AspNetCoreModuleV2\IISLib\IISLib.vcxproj", "{09D9D1D6-2951-4E14-BC35-76A23CF9391A}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandler", "src\AspNetCoreModuleV2\RequestHandler\RequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutOfProcessWebSite", "test\WebSites\OutOfProcessWebSite\OutOfProcessWebSite.csproj", "{42E60F88-E23F-417A-8143-0CCEC05E1D02}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{622D35C9-627B-466E-8D15-752968CC79AF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Performance", "benchmarks\IIS.Performance\IIS.Performance.csproj", "{48F46909-E76A-4788-BCE1-E543C0E140FE}" + ProjectSection(ProjectDependencies) = postProject + {46A8612B-418B-4D70-B3A7-A21DD0627473} = {46A8612B-418B-4D70-B3A7-A21DD0627473} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InProcessRequestHandler", "src\AspNetCoreModuleV2\InProcessRequestHandler\InProcessRequestHandler.vcxproj", "{D57EA297-6DC2-4BC0-8C91-334863327863}" + ProjectSection(ProjectDependencies) = postProject + {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {09D9D1D6-2951-4E14-BC35-76A23CF9391A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OutOfProcessRequestHandler", "src\AspNetCoreModuleV2\OutOfProcessRequestHandler\OutOfProcessRequestHandler.vcxproj", "{7F87406C-A3C8-4139-A68D-E4C344294A67}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "test\gtest\gtest.vcxproj", "{CAC1267B-8778-4257-AAC6-CAF481723B01}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandlerLib", "src\AspNetCoreModuleV2\RequestHandlerLib\RequestHandlerLib.vcxproj", "{1533E271-F61B-441B-8B74-59FB61DF0552}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.FunctionalTests", "test\IIS.FunctionalTests\IIS.FunctionalTests.csproj", "{1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IntegrationTesting.IIS", "src\Microsoft.AspNetCore.Server.IntegrationTesting.IIS\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", "{CE4FB142-91FB-4B34-BC96-A31120EF4009}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.Tests", "test\IIS.Tests\IIS.Tests.csproj", "{A091777D-66B3-42E1-B95C-85322DE40706}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Tests", "test\Common.Tests\Common.Tests.csproj", "{A641A208-2974-4E48-BCFF-54E3AAFA4FB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.BackwardsCompatibility.FunctionalTests", "test\IIS.BackwardsCompatibility.FunctionalTests\IIS.BackwardsCompatibility.FunctionalTests.csproj", "{28055B05-25D4-4F17-9F36-A1D09FDDA607}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.ForwardsCompatibility.FunctionalTests", "test\IIS.ForwardsCompatibility.FunctionalTests\IIS.ForwardsCompatibility.FunctionalTests.csproj", "{F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProcessForwardsCompatWebSite", "test\WebSites\InProcessForwardsCompatWebSite\InProcessWebSite.csproj", "{980DAB60-6471-46EC-82EE-B457D91C3789}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + NativeDebug|Any CPU = NativeDebug|Any CPU + NativeDebug|x64 = NativeDebug|x64 + NativeDebug|x86 = NativeDebug|x86 + NativeRelease|Any CPU = NativeRelease|Any CPU + NativeRelease|x64 = NativeRelease|x64 + NativeRelease|x86 = NativeRelease|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 @@ -106,6 +149,14 @@ Global {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x64.Build.0 = Debug|Any CPU {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x86.ActiveCfg = Debug|Any CPU {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Debug|x86.Build.0 = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.NativeRelease|x86.ActiveCfg = Release|Any CPU {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|Any CPU.Build.0 = Release|Any CPU {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64}.Release|x64.ActiveCfg = Release|Any CPU @@ -118,6 +169,14 @@ Global {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x64.Build.0 = Debug|Any CPU {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x86.ActiveCfg = Debug|Any CPU {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Debug|x86.Build.0 = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.NativeRelease|x86.ActiveCfg = Release|Any CPU {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|Any CPU.Build.0 = Release|Any CPU {8B3446E8-E6A8-4591-AA63-A95837C6E97C}.Release|x64.ActiveCfg = Release|Any CPU @@ -130,28 +189,34 @@ Global {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x64.Build.0 = Debug|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x86.ActiveCfg = Debug|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Debug|x86.Build.0 = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {4106DB10-E09F-480E-9CE6-B39235512EE6}.NativeRelease|x86.ActiveCfg = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|Any CPU.Build.0 = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x64.ActiveCfg = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x64.Build.0 = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x86.ActiveCfg = Release|Any CPU {4106DB10-E09F-480E-9CE6-B39235512EE6}.Release|x86.Build.0 = Release|Any CPU - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Debug|Any CPU.ActiveCfg = Debug|x86 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Debug|x64.ActiveCfg = Debug|x64 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Debug|x64.Build.0 = Debug|x64 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Debug|x86.ActiveCfg = Debug|x86 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Debug|x86.Build.0 = Debug|x86 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Release|Any CPU.ActiveCfg = Release|x86 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Release|x64.ActiveCfg = Release|x64 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Release|x64.Build.0 = Release|x64 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Release|x86.ActiveCfg = Release|x86 - {F54715C3-88D8-49E3-A291-C13570FE81FC}.Release|x86.Build.0 = Release|x86 {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x64.ActiveCfg = Debug|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x64.Build.0 = Debug|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x86.ActiveCfg = Debug|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Debug|x86.Build.0 = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.NativeRelease|x86.ActiveCfg = Release|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|Any CPU.Build.0 = Release|Any CPU {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA}.Release|x64.ActiveCfg = Release|Any CPU @@ -164,18 +229,36 @@ Global {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x64.Build.0 = Debug|x64 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x86.ActiveCfg = Debug|x86 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Debug|x86.Build.0 = Debug|x86 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeDebug|Any CPU.ActiveCfg = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeDebug|Any CPU.Build.0 = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeDebug|x64.ActiveCfg = Debug|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeDebug|x86.ActiveCfg = Debug|x86 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeRelease|Any CPU.ActiveCfg = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeRelease|Any CPU.Build.0 = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeRelease|x64.ActiveCfg = Release|x64 + {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.NativeRelease|x86.ActiveCfg = Release|x86 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|Any CPU.ActiveCfg = Release|x64 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|Any CPU.Build.0 = Release|x64 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x64.ActiveCfg = Release|x64 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x64.Build.0 = Release|x64 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x86.ActiveCfg = Release|x86 {9BC4AFCB-325D-4C81-8228-8CF301CE2F97}.Release|x86.Build.0 = Release|x86 - {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|Any CPU.ActiveCfg = Debug|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|Any CPU.ActiveCfg = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|Any CPU.Build.0 = Debug|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x64.ActiveCfg = Debug|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x64.Build.0 = Debug|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x86.ActiveCfg = Debug|x86 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Debug|x86.Build.0 = Debug|x86 - {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|Any CPU.ActiveCfg = Release|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeDebug|Any CPU.ActiveCfg = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeDebug|Any CPU.Build.0 = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeDebug|x64.ActiveCfg = Debug|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeDebug|x86.ActiveCfg = Debug|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeRelease|Any CPU.ActiveCfg = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeRelease|Any CPU.Build.0 = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeRelease|x64.ActiveCfg = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.NativeRelease|x86.ActiveCfg = Release|x86 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|Any CPU.ActiveCfg = Release|x64 + {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|Any CPU.Build.0 = Release|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.ActiveCfg = Release|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.Build.0 = Release|x64 {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.ActiveCfg = Release|x86 @@ -186,18 +269,36 @@ Global {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x64.Build.0 = Debug|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x86.ActiveCfg = Debug|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Debug|x86.Build.0 = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {46A8612B-418B-4D70-B3A7-A21DD0627473}.NativeRelease|x86.ActiveCfg = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|Any CPU.ActiveCfg = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|Any CPU.Build.0 = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x64.ActiveCfg = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x64.Build.0 = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x86.ActiveCfg = Release|Any CPU {46A8612B-418B-4D70-B3A7-A21DD0627473}.Release|x86.Build.0 = Release|Any CPU - {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.ActiveCfg = Debug|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|Any CPU.Build.0 = Debug|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.ActiveCfg = Debug|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x64.Build.0 = Debug|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.ActiveCfg = Debug|x86 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Debug|x86.Build.0 = Debug|x86 - {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.ActiveCfg = Release|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeDebug|Any CPU.ActiveCfg = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeDebug|Any CPU.Build.0 = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeDebug|x64.ActiveCfg = Debug|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeDebug|x86.ActiveCfg = Debug|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeRelease|Any CPU.ActiveCfg = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeRelease|Any CPU.Build.0 = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeRelease|x64.ActiveCfg = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.NativeRelease|x86.ActiveCfg = Release|x86 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.ActiveCfg = Release|x64 + {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|Any CPU.Build.0 = Release|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.ActiveCfg = Release|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x64.Build.0 = Release|x64 {13FD8F12-FFBE-4D01-B4AC-444F2994B04F}.Release|x86.ActiveCfg = Release|x86 @@ -208,27 +309,55 @@ Global {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x64.Build.0 = Debug|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x86.ActiveCfg = Debug|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Debug|x86.Build.0 = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.NativeRelease|x86.ActiveCfg = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|Any CPU.Build.0 = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x64.ActiveCfg = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x64.Build.0 = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x86.ActiveCfg = Release|Any CPU {064D860B-4D7C-4B1D-918F-E020F1B99E2A}.Release|x86.Build.0 = Release|Any CPU - {FC2A97F8-A749-4C04-97D1-97500066A820}.Debug|Any CPU.ActiveCfg = Debug|x86 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Debug|x64.ActiveCfg = Debug|x64 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Debug|x64.Build.0 = Debug|x64 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Debug|x86.ActiveCfg = Debug|x86 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Debug|x86.Build.0 = Debug|x86 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|Any CPU.ActiveCfg = Release|x86 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x64.ActiveCfg = Release|x64 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x64.Build.0 = Release|x64 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x86.ActiveCfg = Release|x86 - {FC2A97F8-A749-4C04-97D1-97500066A820}.Release|x86.Build.0 = Release|x86 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.ActiveCfg = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x64.Build.0 = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Debug|x86.Build.0 = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeDebug|x64.ActiveCfg = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeDebug|x64.Build.0 = Debug|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeDebug|x86.Build.0 = Debug|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeRelease|x64.ActiveCfg = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeRelease|x64.Build.0 = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeRelease|x86.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.NativeRelease|x86.Build.0 = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|Any CPU.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.ActiveCfg = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x64.Build.0 = Release|x64 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.ActiveCfg = Release|Win32 + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1}.Release|x86.Build.0 = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|Any CPU.ActiveCfg = Debug|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.ActiveCfg = Debug|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x64.Build.0 = Debug|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.ActiveCfg = Debug|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Debug|x86.Build.0 = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeDebug|x64.ActiveCfg = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeDebug|x64.Build.0 = Debug|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeDebug|x86.Build.0 = Debug|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeRelease|x64.ActiveCfg = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeRelease|x64.Build.0 = Release|x64 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeRelease|x86.ActiveCfg = Release|Win32 + {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.NativeRelease|x86.Build.0 = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|Any CPU.ActiveCfg = Release|Win32 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.ActiveCfg = Release|x64 {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}.Release|x64.Build.0 = Release|x64 @@ -239,6 +368,16 @@ Global {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Debug|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.ActiveCfg = Debug|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x86.Build.0 = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeDebug|x64.ActiveCfg = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeDebug|x64.Build.0 = Debug|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeDebug|x86.Build.0 = Debug|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeRelease|x64.ActiveCfg = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeRelease|x64.Build.0 = Release|x64 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeRelease|x86.ActiveCfg = Release|Win32 + {439824F9-1455-4CC4-BD79-B44FA0A16552}.NativeRelease|x86.Build.0 = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|Any CPU.ActiveCfg = Release|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.ActiveCfg = Release|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Release|x64.Build.0 = Release|x64 @@ -249,6 +388,16 @@ Global {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x64.Build.0 = Debug|x64 {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.ActiveCfg = Debug|Win32 {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Debug|x86.Build.0 = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeDebug|x64.ActiveCfg = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeDebug|x64.Build.0 = Debug|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeDebug|x86.Build.0 = Debug|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeRelease|x64.ActiveCfg = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeRelease|x64.Build.0 = Release|x64 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeRelease|x86.ActiveCfg = Release|Win32 + {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.NativeRelease|x86.Build.0 = Release|Win32 {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|Any CPU.ActiveCfg = Release|Win32 {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.ActiveCfg = Release|x64 {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B}.Release|x64.Build.0 = Release|x64 @@ -259,6 +408,16 @@ Global {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x64.Build.0 = Debug|x64 {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.ActiveCfg = Debug|Win32 {55494E58-E061-4C4C-A0A8-837008E72F85}.Debug|x86.Build.0 = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeDebug|x64.ActiveCfg = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeDebug|x64.Build.0 = Debug|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeDebug|x86.Build.0 = Debug|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeRelease|x64.ActiveCfg = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeRelease|x64.Build.0 = Release|x64 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeRelease|x86.ActiveCfg = Release|Win32 + {55494E58-E061-4C4C-A0A8-837008E72F85}.NativeRelease|x86.Build.0 = Release|Win32 {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|Any CPU.ActiveCfg = Release|Win32 {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.ActiveCfg = Release|x64 {55494E58-E061-4C4C-A0A8-837008E72F85}.Release|x64.Build.0 = Release|x64 @@ -269,21 +428,277 @@ Global {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x64.Build.0 = Debug|x64 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.ActiveCfg = Debug|Win32 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Debug|x86.Build.0 = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeDebug|x64.ActiveCfg = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeDebug|x64.Build.0 = Debug|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeDebug|x86.Build.0 = Debug|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeRelease|x64.ActiveCfg = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeRelease|x64.Build.0 = Release|x64 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeRelease|x86.ActiveCfg = Release|Win32 + {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.NativeRelease|x86.Build.0 = Release|Win32 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|Any CPU.ActiveCfg = Release|Win32 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.ActiveCfg = Release|x64 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x64.Build.0 = Release|x64 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.ActiveCfg = Release|Win32 {09D9D1D6-2951-4E14-BC35-76A23CF9391A}.Release|x86.Build.0 = Release|Win32 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|Any CPU.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|Any CPU.Build.0 = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x64.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x64.Build.0 = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x86.ActiveCfg = Debug|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Debug|x86.Build.0 = Debug|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeDebug|Any CPU.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeDebug|Any CPU.Build.0 = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeDebug|x64.ActiveCfg = Debug|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeDebug|x86.ActiveCfg = Debug|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeRelease|Any CPU.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeRelease|Any CPU.Build.0 = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeRelease|x64.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.NativeRelease|x86.ActiveCfg = Release|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|Any CPU.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|Any CPU.Build.0 = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x64.ActiveCfg = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x64.Build.0 = Release|x64 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x86.ActiveCfg = Release|x86 + {42E60F88-E23F-417A-8143-0CCEC05E1D02}.Release|x86.Build.0 = Release|x86 + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x64.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x64.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x86.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Debug|x86.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|Any CPU.Build.0 = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x64.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x64.Build.0 = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x86.ActiveCfg = Release|Any CPU + {48F46909-E76A-4788-BCE1-E543C0E140FE}.Release|x86.Build.0 = Release|Any CPU {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|Any CPU.ActiveCfg = Debug|Win32 {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.ActiveCfg = Debug|x64 {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x64.Build.0 = Debug|x64 {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.ActiveCfg = Debug|Win32 {D57EA297-6DC2-4BC0-8C91-334863327863}.Debug|x86.Build.0 = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeDebug|x64.ActiveCfg = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeDebug|x64.Build.0 = Debug|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeDebug|x86.Build.0 = Debug|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeRelease|x64.ActiveCfg = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeRelease|x64.Build.0 = Release|x64 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeRelease|x86.ActiveCfg = Release|Win32 + {D57EA297-6DC2-4BC0-8C91-334863327863}.NativeRelease|x86.Build.0 = Release|Win32 {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|Any CPU.ActiveCfg = Release|Win32 {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.ActiveCfg = Release|x64 {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x64.Build.0 = Release|x64 {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.ActiveCfg = Release|Win32 {D57EA297-6DC2-4BC0-8C91-334863327863}.Release|x86.Build.0 = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.ActiveCfg = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x64.Build.0 = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Debug|x86.Build.0 = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeDebug|x64.ActiveCfg = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeDebug|x64.Build.0 = Debug|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeDebug|x86.Build.0 = Debug|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeRelease|x64.ActiveCfg = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeRelease|x64.Build.0 = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeRelease|x86.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.NativeRelease|x86.Build.0 = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|Any CPU.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.ActiveCfg = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x64.Build.0 = Release|x64 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.ActiveCfg = Release|Win32 + {7F87406C-A3C8-4139-A68D-E4C344294A67}.Release|x86.Build.0 = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.ActiveCfg = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x64.Build.0 = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Debug|x86.Build.0 = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeDebug|x64.ActiveCfg = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeDebug|x64.Build.0 = Debug|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeDebug|x86.Build.0 = Debug|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeRelease|x64.ActiveCfg = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeRelease|x64.Build.0 = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeRelease|x86.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.NativeRelease|x86.Build.0 = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|Any CPU.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.ActiveCfg = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x64.Build.0 = Release|x64 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.ActiveCfg = Release|Win32 + {CAC1267B-8778-4257-AAC6-CAF481723B01}.Release|x86.Build.0 = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.ActiveCfg = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x64.Build.0 = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Debug|x86.Build.0 = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeDebug|Any CPU.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeDebug|x64.ActiveCfg = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeDebug|x64.Build.0 = Debug|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeDebug|x86.ActiveCfg = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeDebug|x86.Build.0 = Debug|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeRelease|Any CPU.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeRelease|x64.ActiveCfg = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeRelease|x64.Build.0 = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeRelease|x86.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.NativeRelease|x86.Build.0 = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|Any CPU.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.ActiveCfg = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x64.Build.0 = Release|x64 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.ActiveCfg = Release|Win32 + {1533E271-F61B-441B-8B74-59FB61DF0552}.Release|x86.Build.0 = Release|Win32 + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|x64.Build.0 = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Debug|x86.Build.0 = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|Any CPU.Build.0 = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|x64.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|x64.Build.0 = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|x86.ActiveCfg = Release|Any CPU + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712}.Release|x86.Build.0 = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|x64.Build.0 = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|x86.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Debug|x86.Build.0 = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|Any CPU.Build.0 = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x64.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x64.Build.0 = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x86.ActiveCfg = Release|Any CPU + {CE4FB142-91FB-4B34-BC96-A31120EF4009}.Release|x86.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x64.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x64.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x86.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Debug|x86.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|Any CPU.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x64.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x64.Build.0 = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x86.ActiveCfg = Release|Any CPU + {A091777D-66B3-42E1-B95C-85322DE40706}.Release|x86.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x64.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Debug|x86.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|Any CPU.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x64.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x64.Build.0 = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x86.ActiveCfg = Release|Any CPU + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9}.Release|x86.Build.0 = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|x64.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|x64.Build.0 = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|x86.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Debug|x86.Build.0 = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|Any CPU.Build.0 = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|x64.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|x64.Build.0 = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|x86.ActiveCfg = Release|Any CPU + {28055B05-25D4-4F17-9F36-A1D09FDDA607}.Release|x86.Build.0 = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeDebug|Any CPU.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeDebug|Any CPU.Build.0 = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeDebug|x64.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeDebug|x86.ActiveCfg = Debug|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeRelease|Any CPU.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeRelease|Any CPU.Build.0 = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeRelease|x64.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.NativeRelease|x86.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|Any CPU.Build.0 = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|x64.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|x64.Build.0 = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|x86.ActiveCfg = Release|Any CPU + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F}.Release|x86.Build.0 = Release|Any CPU + {980DAB60-6471-46EC-82EE-B457D91C3789}.Debug|Any CPU.ActiveCfg = Debug|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Debug|x64.ActiveCfg = Debug|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Debug|x64.Build.0 = Debug|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Debug|x86.ActiveCfg = Debug|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Debug|x86.Build.0 = Debug|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeDebug|Any CPU.ActiveCfg = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeDebug|Any CPU.Build.0 = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeDebug|x64.ActiveCfg = Debug|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeDebug|x86.ActiveCfg = Debug|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeRelease|Any CPU.ActiveCfg = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeRelease|Any CPU.Build.0 = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeRelease|x64.ActiveCfg = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.NativeRelease|x86.ActiveCfg = Release|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Release|Any CPU.ActiveCfg = Release|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Release|x64.ActiveCfg = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Release|x64.Build.0 = Release|x64 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Release|x86.ActiveCfg = Release|x86 + {980DAB60-6471-46EC-82EE-B457D91C3789}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -292,7 +707,6 @@ Global {E4E2BDC4-A9C6-4AE9-B429-032EC83EDE64} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} {8B3446E8-E6A8-4591-AA63-A95837C6E97C} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {4106DB10-E09F-480E-9CE6-B39235512EE6} = {EF30B533-D715-421A-92B7-92FEF460AC9C} - {F54715C3-88D8-49E3-A291-C13570FE81FC} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {9BC4AFCB-325D-4C81-8228-8CF301CE2F97} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} {679FA2A2-898B-4320-884E-C2D294A97CE1} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} @@ -300,7 +714,7 @@ Global {13FD8F12-FFBE-4D01-B4AC-444F2994B04F} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} {064D860B-4D7C-4B1D-918F-E020F1B99E2A} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} = {EF30B533-D715-421A-92B7-92FEF460AC9C} - {FC2A97F8-A749-4C04-97D1-97500066A820} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {1EAC8125-1765-4E2D-8CBE-56DC98A1C8C1} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {16E521CE-77F1-4B1C-A183-520A41C4F372} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {06CA2C2B-83B0-4D83-905A-E0C74790009E} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {16E521CE-77F1-4B1C-A183-520A41C4F372} @@ -308,7 +722,19 @@ Global {EC82302F-D2F0-4727-99D1-EABC0DD9DC3B} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} {55494E58-E061-4C4C-A0A8-837008E72F85} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} {09D9D1D6-2951-4E14-BC35-76A23CF9391A} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {42E60F88-E23F-417A-8143-0CCEC05E1D02} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} + {48F46909-E76A-4788-BCE1-E543C0E140FE} = {622D35C9-627B-466E-8D15-752968CC79AF} {D57EA297-6DC2-4BC0-8C91-334863327863} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {7F87406C-A3C8-4139-A68D-E4C344294A67} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {CAC1267B-8778-4257-AAC6-CAF481723B01} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {1533E271-F61B-441B-8B74-59FB61DF0552} = {06CA2C2B-83B0-4D83-905A-E0C74790009E} + {1F0C8D9B-F47B-41F3-9FC9-6954B6DC7712} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {CE4FB142-91FB-4B34-BC96-A31120EF4009} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} + {A091777D-66B3-42E1-B95C-85322DE40706} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {A641A208-2974-4E48-BCFF-54E3AAFA4FB9} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {28055B05-25D4-4F17-9F36-A1D09FDDA607} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {F6FAA65F-AA29-4DDA-AA89-C16AF4A69F9F} = {EF30B533-D715-421A-92B7-92FEF460AC9C} + {980DAB60-6471-46EC-82EE-B457D91C3789} = {744ACDC6-F6A0-4FF9-9421-F25C5F2DC520} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB4F868D-E1AE-4FD7-9333-69FA15B268C5} diff --git a/src/IISIntegration/NuGetPackageVerifier.json b/src/IISIntegration/NuGetPackageVerifier.json index eea701bb65..a20c45992c 100644 --- a/src/IISIntegration/NuGetPackageVerifier.json +++ b/src/IISIntegration/NuGetPackageVerifier.json @@ -3,7 +3,7 @@ "rules": [], "packages": { "Microsoft.AspNetCore.AspNetCoreModule": {}, - "Microsoft.AspNetCore.AspNetCoreModuleV1": {} + "Microsoft.AspNetCore.AspNetCoreModuleV2": {} } }, "Default": { diff --git a/src/IISIntegration/README.md b/src/IISIntegration/README.md deleted file mode 100644 index f9310e5d9e..0000000000 --- a/src/IISIntegration/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This repo hosts the ASP.NET Core middleware for IIS integration and the ASP.NET Core Module. - -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/IISIntegration/benchmarks/IIS.Performance/FirstRequestConfig.cs b/src/IISIntegration/benchmarks/IIS.Performance/FirstRequestConfig.cs new file mode 100644 index 0000000000..727746871f --- /dev/null +++ b/src/IISIntegration/benchmarks/IIS.Performance/FirstRequestConfig.cs @@ -0,0 +1,39 @@ +// 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 BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class FirstRequestConfig : ManualConfig + { + public FirstRequestConfig() + { + Add(ConsoleLogger.Default); + Add(MarkdownExporter.GitHub); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(DefaultColumnProviders.Instance); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.Core + .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) + .With(new GcMode { Server = true }) + .WithTargetCount(10) + .WithInvocationCount(1) + .WithUnrollFactor(1) + .With(RunStrategy.ColdStart)); + } + } +} diff --git a/src/IISIntegration/benchmarks/IIS.Performance/IIS.Performance.csproj b/src/IISIntegration/benchmarks/IIS.Performance/IIS.Performance.csproj new file mode 100644 index 0000000000..6373cfdfa7 --- /dev/null +++ b/src/IISIntegration/benchmarks/IIS.Performance/IIS.Performance.csproj @@ -0,0 +1,46 @@ + + + + + IIS.Performance + Microsoft.AspNetCore.Server.IIS.Performance + netcoreapp2.2 + Exe + true + true + false + + + + + + + + + + + + + + + + + + + False + + + False + + + False + + + + + + + + + + \ No newline at end of file diff --git a/src/IISIntegration/benchmarks/IIS.Performance/PlaintextBenchmark.cs b/src/IISIntegration/benchmarks/IIS.Performance/PlaintextBenchmark.cs new file mode 100644 index 0000000000..8c6b4b2cd3 --- /dev/null +++ b/src/IISIntegration/benchmarks/IIS.Performance/PlaintextBenchmark.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IIS.Performance +{ + [AspNetCoreBenchmark] + public class PlaintextBenchmark + { + private TestServer _server; + + private HttpClient _client; + + [GlobalSetup] + public void Setup() + { + _server = TestServer.Create(builder => builder.UseMiddleware(), new LoggerFactory()).GetAwaiter().GetResult(); + // Recreate client, TestServer.Client has additional logging that can hurt performance + _client = new HttpClient() + { + BaseAddress = _server.HttpClient.BaseAddress + }; + } + + [Benchmark] + public async Task Plaintext() + { + await _client.GetAsync("/plaintext"); + } + + // Copied from https://github.com/aspnet/benchmarks/blob/dev/src/Benchmarks/Middleware/PlaintextMiddleware.cs + public class PlaintextMiddleware + { + private static readonly PathString _path = new PathString("/plaintext"); + private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!"); + + private readonly RequestDelegate _next; + + public PlaintextMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task Invoke(HttpContext httpContext) + { + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) + { + return WriteResponse(httpContext.Response); + } + + return _next(httpContext); + } + + public static Task WriteResponse(HttpResponse response) + { + var payloadLength = _helloWorldPayload.Length; + response.StatusCode = 200; + response.ContentType = "text/plain"; + response.ContentLength = payloadLength; + return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength); + } +} + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs b/src/IISIntegration/benchmarks/IIS.Performance/StartupTimeBenchmark.cs similarity index 51% rename from src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs rename to src/IISIntegration/benchmarks/IIS.Performance/StartupTimeBenchmark.cs index 763603508d..ef66b6b671 100644 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/src/IISIntegration/benchmarks/IIS.Performance/StartupTimeBenchmark.cs @@ -1,53 +1,50 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.IO; using System.Net.Http; -using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +namespace Microsoft.AspNetCore.Server.IIS.Performance { - public class IISTestSiteFixture : IDisposable + [AspNetCoreBenchmark(typeof(FirstRequestConfig))] + public class StartupTimeBenchmark { - private readonly IApplicationDeployer _deployer; + private ApplicationDeployer _deployer; + public HttpClient _client; - public IISTestSiteFixture() + [IterationSetup] + public void Setup() { - var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), + var deploymentParameters = new DeploymentParameters(Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "test/Websites/InProcessWebSite"), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { - ServerConfigTemplateContent = File.ReadAllText("AppHostConfig/Http.config"), + ServerConfigTemplateContent = File.ReadAllText("IISExpress.config"), SiteName = "HttpTestSite", TargetFramework = "netcoreapp2.1", ApplicationType = ApplicationType.Portable, - Configuration = -#if DEBUG - "Debug" -#else - "Release" -#endif + AncmVersion = AncmVersion.AspNetCoreModuleV2 }; - _deployer = ApplicationDeployerFactory.Create(deploymentParameters, NullLoggerFactory.Instance); - DeploymentResult = _deployer.DeployAsync().Result; - Client = DeploymentResult.HttpClient; - BaseUri = DeploymentResult.ApplicationBaseUri; - ShutdownToken = DeploymentResult.HostShutdownToken; + _client = _deployer.DeployAsync().Result.HttpClient; } - public string BaseUri { get; } - public HttpClient Client { get; } - public CancellationToken ShutdownToken { get; } - public DeploymentResult DeploymentResult { get; } - - public void Dispose() + [IterationCleanup] + public void Cleanup() { _deployer.Dispose(); } + + [Benchmark] + public async Task SendFirstRequest() + { + var response = await _client.GetAsync(""); + } } } diff --git a/src/IISIntegration/build/Build.Settings b/src/IISIntegration/build/Build.Settings index 9daccf4aaa..d60b07c269 100644 --- a/src/IISIntegration/build/Build.Settings +++ b/src/IISIntegration/build/Build.Settings @@ -20,6 +20,11 @@ false true + ..\DefaultRules.ruleset + false + + true + $(RunCodeAnalysis) diff --git a/src/IISIntegration/build/applicationhost.config b/src/IISIntegration/build/applicationhost.config index 9af39ac631..ce00a887b4 100644 --- a/src/IISIntegration/build/applicationhost.config +++ b/src/IISIntegration/build/applicationhost.config @@ -233,6 +233,7 @@ + @@ -875,6 +876,7 @@ + diff --git a/src/IISIntegration/build/applicationhost.iis.config b/src/IISIntegration/build/applicationhost.iis.config index 1998fc2d86..34a4da59e6 100644 --- a/src/IISIntegration/build/applicationhost.iis.config +++ b/src/IISIntegration/build/applicationhost.iis.config @@ -226,6 +226,7 @@ + @@ -288,6 +289,7 @@ + diff --git a/src/IISIntegration/build/assets.props b/src/IISIntegration/build/assets.props new file mode 100644 index 0000000000..13b30556bd --- /dev/null +++ b/src/IISIntegration/build/assets.props @@ -0,0 +1,269 @@ + + + + true + x64 + $(Platform) + Win32 + $(NativePlatform) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(MSBuildThisFileDirectory)..\src\AspNetCoreModuleV1\AspNetCore\bin\$(Configuration)\$(NativeVCPlatform)\aspnetcore.dll + $(MSBuildThisFileDirectory)..\src\AspNetCoreModuleV2\AspNetCore\bin\$(Configuration)\$(NativeVCPlatform)\aspnetcorev2.dll + $(MSBuildThisFileDirectory)..\src\AspNetCoreModuleV2\InProcessRequestHandler\bin\$(Configuration)\$(NativeVCPlatform)\aspnetcorev2_inprocess.dll + $(MSBuildThisFileDirectory)..\src\AspNetCoreModuleV2\OutOfProcessRequestHandler\bin\$(Configuration)\$(NativeVCPlatform)\aspnetcorev2_outofprocess.dll + + diff --git a/src/IISIntegration/build/build.msbuild b/src/IISIntegration/build/build.msbuild deleted file mode 100644 index 117ed59bec..0000000000 --- a/src/IISIntegration/build/build.msbuild +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/build/buildpipeline/pipeline.groovy b/src/IISIntegration/build/buildpipeline/pipeline.groovy new file mode 100644 index 0000000000..13fd8d9add --- /dev/null +++ b/src/IISIntegration/build/buildpipeline/pipeline.groovy @@ -0,0 +1,18 @@ +import org.dotnet.ci.pipelines.Pipeline + +def windowsPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/windows.groovy') + +def configurations = [ + 'Debug', + 'Release' +] + +configurations.each { configuration -> + + def params = [ + 'Configuration': configuration + ] + + windowsPipeline.triggerPipelineOnEveryGithubPR("Windows ${configuration} x64 Build", params) + windowsPipeline.triggerPipelineOnGithubPush(params) +} diff --git a/src/IISIntegration/build/buildpipeline/windows-appverif.groovy b/src/IISIntegration/build/buildpipeline/windows-appverif.groovy new file mode 100644 index 0000000000..0c1a6affe8 --- /dev/null +++ b/src/IISIntegration/build/buildpipeline/windows-appverif.groovy @@ -0,0 +1,15 @@ +@Library('dotnet-ci') _ + +// 'node' indicates to Jenkins that the enclosed block runs on a node that matches +// the label 'windows-with-vs' +simpleNode('Windows.10.Amd64.EnterpriseRS3.ASPNET.Open') { + stage ('Checking out source') { + checkout scm + bat 'git submodule update --init --recursive' + } + stage ('Build') { + def logFolder = getLogFolder() + def environment = "\$env:ASPNETCORE_TEST_LOG_DIR='${WORKSPACE}\\${logFolder}'" + bat "powershell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command \"${environment};&.\\tools\\SetupTestEnvironment.ps1 Setup;&.\\tools\\update_schema.ps1;&.\\tools\\UpdateIISExpressCertificate.ps1;&.\\run.cmd -CI default-build /p:Configuration=${params.Configuration}\";" + } +} diff --git a/src/IISIntegration/build/buildpipeline/windows.groovy b/src/IISIntegration/build/buildpipeline/windows.groovy new file mode 100644 index 0000000000..c482e84079 --- /dev/null +++ b/src/IISIntegration/build/buildpipeline/windows.groovy @@ -0,0 +1,15 @@ +@Library('dotnet-ci') _ + +// 'node' indicates to Jenkins that the enclosed block runs on a node that matches +// the label 'windows-with-vs' +simpleNode('Windows.10.Amd64.EnterpriseRS3.ASPNET.Open') { + stage ('Checking out source') { + checkout scm + bat 'git submodule update --init --recursive' + } + stage ('Build') { + def logFolder = getLogFolder() + def environment = "\$env:ASPNETCORE_TEST_LOG_DIR='${WORKSPACE}\\${logFolder}'" + bat "powershell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command \"${environment};&.\\tools\\SetupTestEnvironment.ps1 SetupDumps;&.\\tools\\update_schema.ps1;&.\\tools\\UpdateIISExpressCertificate.ps1;&.\\run.cmd -CI default-build /p:SkipIISBackwardsCompatibilityTests=true /p:SkipIISForwardsCompatibilityTests=true /p:Configuration=${params.Configuration}\";" + } +} diff --git a/src/IISIntegration/build/dependencies.props b/src/IISIntegration/build/dependencies.props index c90801a573..5686841850 100644 --- a/src/IISIntegration/build/dependencies.props +++ b/src/IISIntegration/build/dependencies.props @@ -1,55 +1,69 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - 2.1.3-rtm-15802 - 15.6.82 - 15.6.82 - 2.0.0 - 2.1.2 + + 0.10.13 + 3.0.0-alpha1-20181011.11 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 0.7.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 15.8.166 + 15.8.166 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 3.0.0-alpha1-10657 + 2.0.9 + 2.1.3 + 2.2.0-preview3-27014-02 + 1.0.1 + 3.0.0-alpha1-10657 15.6.1 + 11.1.0 2.0.3 - 4.5.0 - 4.5.0 - 4.5.1 - 4.5.1 - 4.5.0 - 4.5.1 - 4.5.0 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26717-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 + 4.6.0-preview1-26907-04 9.0.1 2.3.1 - 2.4.0-beta.1.build3945 + 2.4.0 - - - - - 2.1.2 - 2.1.1 - 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.0 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 - 2.1.1 + 2.2.0-preview3-35497 + 2.2.0-preview3-35497 + 2.2.0-preview3-35497 + 2.1.0 + 2.1.0 + 2.1.0 + 2.1.0 + 4.5.2 + 4.5.0 diff --git a/src/IISIntegration/build/functional-test-assets.targets b/src/IISIntegration/build/functional-test-assets.targets new file mode 100644 index 0000000000..81fb82b4a3 --- /dev/null +++ b/src/IISIntegration/build/functional-test-assets.targets @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/IISIntegration/build/launchSettings.json b/src/IISIntegration/build/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/build/launchSettings.json +++ b/src/IISIntegration/build/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/build/repo.props b/src/IISIntegration/build/repo.props index c3e3c1c227..69111ae363 100644 --- a/src/IISIntegration/build/repo.props +++ b/src/IISIntegration/build/repo.props @@ -2,32 +2,42 @@ - Microsoft $(BuildDir)AspNetCoreModule.zip + $(BuildDir)StressTestWebSite.zip - - - - - + + + + + + + + + + + + + + + + + Internal.AspNetCore.Universe.Lineup - 2.1.0-rc1-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json - + - diff --git a/src/IISIntegration/build/repo.targets b/src/IISIntegration/build/repo.targets index c39d7367c5..3e937d6bf9 100644 --- a/src/IISIntegration/build/repo.targets +++ b/src/IISIntegration/build/repo.targets @@ -1,96 +1,112 @@ + $(PrepareDependsOn) + $(GetArtifactInfoDependsOn);GetNativeArtifactsInfo BuildNativeAssets;$(CompileDependsOn) - $(PackageDependsOn);PackageNativeProjects + $(PackageDependsOn);PackageNativeProjects;PackageStressTestApp + $(TestDependsOn);RunNativeTest $(RepositoryRoot)NuGetPackageVerifier.xplat.json + $(RepositoryRoot)src\ + bin\$(Configuration)\ + + + + + + - -p:Configuration=$(Configuration) -v:m -nologo -clp:NoSummary -p:CommitHash=$(CommitHash) + -p:Configuration=Native$(Configuration) -v:m -nologo -clp:NoSummary -p:CommitHash=$(CommitHash) -m - + - - - - - - NuGetPackage - Microsoft.AspNetCore.AspNetCoreModule - $(PackageVersion) - $(RepositoryRoot) - - + + + $(BuildDir)Microsoft.AspNetCore.AspNetCoreModule.$(PackageVersion).nupkg + $(BuildDir)Microsoft.AspNetCore.AspNetCoreModuleV2.$(PackageVersion).nupkg + - - NuGetPackage - Microsoft.AspNetCore.AspNetCoreModuleV1 - $(PackageVersion) - $(RepositoryRoot) - - + + + NuGetPackage + Microsoft.AspNetCore.AspNetCoreModule + $(PackageVersion) + $(RepositoryRoot) + + + - - ZipArchive - $(RepositoryRoot) - shipoob - + + NuGetPackage + Microsoft.AspNetCore.AspNetCoreModuleV2 + $(PackageVersion) + $(RepositoryRoot) + + - - - - - - - - + + ZipArchive + $(RepositoryRoot) + shipoob + + + + ZipArchive + $(RepositoryRoot) + noship + + + + + + + - + $(MSBuildThisFileDirectory)..\nuget\Microsoft.AspNetCore.AspNetCoreModuleV2.props.in + $(MSBuildThisFileDirectory)..\artifacts\Microsoft.AspNetCore.AspNetCoreModuleV2.props + + + + + - - - - - - - - - - - - - - - + + + - - - + + + + + + -p:Configuration=$(Configuration) -v:m -nologo -clp:NoSummary + + + + + + + $(MSBuildThisFileDirectory)..\test\WebSites\StressTestWebSite\ + $(StressAppBasePath)bin\published\ + + + + + + + + + + + diff --git a/src/IISIntegration/build/testsite.props b/src/IISIntegration/build/testsite.props index d2f6acea7e..74f205b4cf 100644 --- a/src/IISIntegration/build/testsite.props +++ b/src/IISIntegration/build/testsite.props @@ -5,10 +5,12 @@ x64;x86 $(MSBuildThisFileDirectory)applicationhost.config $(MSBuildThisFileDirectory)applicationhost.iis.config - x64 - $(Platform) + false + True - + + + $(MSBuildProgramFiles32)\IIS Express\iisexpress.exe $(SystemRoot)\SysWOW64\inetsrv\w3wp.exe @@ -22,51 +24,38 @@ - $(NativePlatform)\ - + - - - - - - - - - - - + - /config:"$(IISExpressAppHostConfig)" + /config:"$(IISExpressAppHostConfig)" /systray:false -h "$(IISAppHostConfig)" - $(NativePlatform)\aspnetcore.dll - $(NativePlatform)\aspnetcorerh.dll + aspnetcorev2_inprocess.dll $(userprofile)\.dotnet\$(NativePlatform)\dotnet.exe - + - + False - - + $(MSBuildThisFileDirectory)..\test\TestTasks\bin\$(Configuration)\$(TargetFramework)\TestTasks - $(InjectDepsAssembly) - "win7-$(NativePlatform)" "$(AncmRHPath)" + "win7-$(NativePlatform)" "$(AncmInProcessRHPath)" @@ -81,11 +70,11 @@ - + - + diff --git a/src/IISIntegration/korebuild.json b/src/IISIntegration/korebuild.json index 663f878233..b76e84272b 100644 --- a/src/IISIntegration/korebuild.json +++ b/src/IISIntegration/korebuild.json @@ -1,6 +1,6 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json", - "channel": "release/2.1", + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", + "channel": "master", "toolsets": { "visualstudio": { "required": ["Windows"], @@ -14,4 +14,4 @@ ] } } - } \ No newline at end of file + } diff --git a/src/IISIntegration/nuget/AspNetCoreV2.nuspec b/src/IISIntegration/nuget/AspNetCoreV2.nuspec deleted file mode 100644 index 10745f52c4..0000000000 --- a/src/IISIntegration/nuget/AspNetCoreV2.nuspec +++ /dev/null @@ -1,31 +0,0 @@ - - - - Microsoft.AspNetCore.AspNetCoreModule - Microsoft ASP.NET Core Module - $VERSION$ - Microsoft - Microsoft - https://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm - © .NET Foundation. All rights reserved. - https://www.asp.net/ - https://go.microsoft.com/fwlink/?LinkID=288859 - true - ASP.NET Core Module - en-US - Microsoft.AspNetCore.AspNetCoreModule - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/nuget/AspNetCoreV1.nuspec b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.nuspec similarity index 65% rename from src/IISIntegration/nuget/AspNetCoreV1.nuspec rename to src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.nuspec index f89b7ff754..f05fcbbdd8 100644 --- a/src/IISIntegration/nuget/AspNetCoreV1.nuspec +++ b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.nuspec @@ -1,7 +1,7 @@ - Microsoft.AspNetCore.AspNetCoreModuleV1 + Microsoft.AspNetCore.AspNetCoreModule Microsoft ASP.NET Core Module $VERSION$ Microsoft @@ -13,15 +13,19 @@ true ASP.NET Core Module en-US - Microsoft.AspNetCore.AspNetCoreModuleV1 + Microsoft.AspNetCore.AspNetCoreModule + - - - + + + + + + diff --git a/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.props index 5b01ee63a4..7a761813b4 100644 --- a/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.props +++ b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -3,8 +3,6 @@ $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcore.dll $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcore.dll - $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorerh.dll - $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorerh.dll diff --git a/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.nuspec b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.nuspec new file mode 100644 index 0000000000..bc33d6121c --- /dev/null +++ b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.nuspec @@ -0,0 +1,44 @@ + + + + Microsoft.AspNetCore.AspNetCoreModuleV2 + Microsoft ASP.NET Core Module + $VERSION$ + Microsoft + Microsoft + https://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm + © .NET Foundation. All rights reserved. + https://www.asp.net/ + https://go.microsoft.com/fwlink/?LinkID=288859 + true + ASP.NET Core Module + en-US + Microsoft.AspNetCore.AspNetCoreModule + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.props.in b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.props.in new file mode 100644 index 0000000000..09af105513 --- /dev/null +++ b/src/IISIntegration/nuget/Microsoft.AspNetCore.AspNetCoreModuleV2.props.in @@ -0,0 +1,13 @@ + + + + ${AspNetCoreModuleOutOfProcessVersion} + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorev2.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorev2.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorev2_inprocess.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorev2_inprocess.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\$(AspNetCoreModuleOutOfProcessVersion)\aspnetcorev2_outofprocess.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\$(AspNetCoreModuleOutOfProcessVersion)\aspnetcorev2_outofprocess.dll + + + diff --git a/src/IISIntegration/samples/IISSample/IISSample.csproj b/src/IISIntegration/samples/IISSample/IISSample.csproj index f8dafd69ef..556945519e 100644 --- a/src/IISIntegration/samples/IISSample/IISSample.csproj +++ b/src/IISIntegration/samples/IISSample/IISSample.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1;net461 + netcoreapp2.2;net461 diff --git a/src/IISIntegration/samples/IISSample/Startup.cs b/src/IISIntegration/samples/IISSample/Startup.cs index 138f993d9e..f99b6ad729 100644 --- a/src/IISIntegration/samples/IISSample/Startup.cs +++ b/src/IISIntegration/samples/IISSample/Startup.cs @@ -1,3 +1,6 @@ +// 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.Linq; using Microsoft.AspNetCore.Authentication; diff --git a/src/IISIntegration/samples/IISSample/web.config b/src/IISIntegration/samples/IISSample/web.config index 1ee540eb0f..ca998a82a4 100644 --- a/src/IISIntegration/samples/IISSample/web.config +++ b/src/IISIntegration/samples/IISSample/web.config @@ -9,11 +9,11 @@ - + - + diff --git a/src/IISIntegration/samples/NativeIISSample/NativeIISSample.csproj b/src/IISIntegration/samples/NativeIISSample/NativeIISSample.csproj index d9bf22ba16..5fcf5c96a8 100644 --- a/src/IISIntegration/samples/NativeIISSample/NativeIISSample.csproj +++ b/src/IISIntegration/samples/NativeIISSample/NativeIISSample.csproj @@ -1,20 +1,24 @@ - + - netcoreapp2.1 + netcoreapp2.2 + true - + + + + - inprocess + inprocess diff --git a/src/IISIntegration/samples/NativeIISSample/Properties/launchSettings.json b/src/IISIntegration/samples/NativeIISSample/Properties/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/samples/NativeIISSample/Properties/launchSettings.json +++ b/src/IISIntegration/samples/NativeIISSample/Properties/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/samples/NativeIISSample/Startup.cs b/src/IISIntegration/samples/NativeIISSample/Startup.cs index d36f26908d..2b18ff895c 100644 --- a/src/IISIntegration/samples/NativeIISSample/Startup.cs +++ b/src/IISIntegration/samples/NativeIISSample/Startup.cs @@ -8,14 +8,21 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.AspNetCore.Server.IIS; namespace NativeIISSample { public class Startup { + private readonly IAuthenticationSchemeProvider _authSchemeProvider; + + public Startup(IAuthenticationSchemeProvider authSchemeProvider = null) + { + _authSchemeProvider = authSchemeProvider; + } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider) + public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Run(async (context) => { @@ -41,8 +48,11 @@ namespace NativeIISSample await context.Response.WriteAsync(Environment.NewLine); await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine); - var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme); - await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine); + if (_authSchemeProvider != null) + { + var scheme = await _authSchemeProvider.GetSchemeAsync(IISServerDefaults.AuthenticationScheme); + await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine); + } await context.Response.WriteAsync(Environment.NewLine); @@ -65,6 +75,12 @@ namespace NativeIISSample // accessing IIS server variables await context.Response.WriteAsync("Server Variables:" + Environment.NewLine); + foreach (var varName in IISServerVarNames) + { + await context.Response.WriteAsync(varName + ": " + context.GetIISServerVariable(varName) + Environment.NewLine); + } + + await context.Response.WriteAsync(Environment.NewLine); if (context.Features.Get() != null) { await context.Response.WriteAsync("Websocket feature is enabled."); @@ -75,9 +91,25 @@ namespace NativeIISSample } }); } + + private static readonly string[] IISServerVarNames = + { + "AUTH_TYPE", + "AUTH_USER", + "CONTENT_TYPE", + "HTTP_HOST", + "HTTPS", + "REMOTE_PORT", + "REMOTE_USER", + "REQUEST_METHOD", + "WEBSOCKET_VERSION" + }; + public static void Main(string[] args) { var host = new WebHostBuilder() + .UseKestrel() + .UseIIS() .UseIISIntegration() .UseStartup() .Build(); diff --git a/src/IISIntegration/samples/NativeIISSample/web.config b/src/IISIntegration/samples/NativeIISSample/web.config index 08baab0922..b366ebfd8c 100644 --- a/src/IISIntegration/samples/NativeIISSample/web.config +++ b/src/IISIntegration/samples/NativeIISSample/web.config @@ -2,7 +2,7 @@ - + diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj index e1c11dddb6..169c79e503 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/AspNetCore.vcxproj @@ -213,14 +213,6 @@ - - - PreserveNewest - - - - - diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h index 64dde656b0..6330be8879 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h @@ -11,10 +11,11 @@ #define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." #define ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG L"Application '%s' failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x : %x." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x' : %x." #define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" #define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" #define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." #define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." #define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." #define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"App_offline file '%s' was detected." +#define ASPNETCORE_EVENT_PROCESS_SHUTDOWN_MSG L"Application '%s' with physical root '%s' shut down process with Id '%d' listening on port '%d'" diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h index 0eebdb9d69..94b65bad54 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h @@ -67,6 +67,14 @@ public: return m_fReady; } + BOOL + IsDebuggerAttached( + VOID + ) + { + return m_fDebuggerAttached; + } + VOID StopProcess( VOID @@ -280,6 +288,7 @@ private: BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; + BOOL m_fDebuggerAttached; STTIMER m_Timer; SOCKET m_socket; diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc index 50bf87b656..73eb713f1c 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.mc @@ -1,6 +1,7 @@ ;/*++ ; -;Copyright (c) 2014 Microsoft Corporation +; Copyright (c) .NET Foundation. All rights reserved. +; Licensed under the MIT License. See License.txt in the project root for license information. ; ;Module Name: ; @@ -73,6 +74,12 @@ Language=English %1 . +Messageid=1030 +SymbolicName=ASPNETCORE_EVENT_PROCESS_SHUTDOWN +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc index d37eb29238..4bd467538f 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc @@ -10,7 +10,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash +#define FileDescription "IIS ASP.NET Core Module. Commit: " CommitHash ///////////////////////////////////////////////////////////////////////////// // @@ -76,11 +76,11 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "CompanyName", "Microsoft" + VALUE "CompanyName", "Microsoft Corporation" VALUE "FileDescription", FileDescription VALUE "FileVersion", FileVersionStr - VALUE "InternalName", "aspnetcore.dll" - VALUE "LegalCopyright", "Copyright (C) 2016" + VALUE "InternalName", "aspnetcore" + VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" VALUE "OriginalFilename", "aspnetcore.dll" VALUE "ProductName", "ASP.NET Core Module" VALUE "ProductVersion", ProductVersionStr diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx index 086ed8774f..c3fd9f1882 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx @@ -38,7 +38,7 @@ FILE_WATCHER::Create( m_hChangeNotificationThread = CreateThread(NULL, 0, - ChangeNotificationThread, + (LPTHREAD_START_ROUTINE)ChangeNotificationThread, this, 0, NULL); diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx index f02a318f41..1665539f18 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx @@ -883,6 +883,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( HRESULT hr = S_OK; PCWSTR pszVersion = NULL; PCSTR pszVerb; + DWORD dwTimeout = INFINITE; STACK_STRU(strVerb, 32); // @@ -923,11 +924,16 @@ FORWARDING_HANDLER::CreateWinHttpRequest( goto Finished; } + if (!pServerProcess->IsDebuggerAttached()) + { + dwTimeout = pProtocol->QueryTimeout(); + } + if (!WinHttpSetTimeouts(m_hRequest, - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout())) + dwTimeout, //resolve timeout + dwTimeout, // connect timeout + dwTimeout, // send timeout + dwTimeout)) // receive timeout { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx index be5e250df5..b3fd02432a 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx @@ -35,6 +35,7 @@ SERVER_PROCESS::Initialize( m_fBasicAuthEnabled = fBasicAuthEnabled; m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; m_pProcessManager->ReferenceProcessManager(); + m_fDebuggerAttached = FALSE; if (FAILED (hr = m_ProcessPath.Copy(*pszProcessExePath)) || FAILED (hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| @@ -131,7 +132,7 @@ SERVER_PROCESS::SetupListenPort( pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); if (pEntry != NULL) { - if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') + if (pEntry->QueryValue() != NULL && pEntry->QueryValue()[0] != L'\0') { m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); if(m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) @@ -149,6 +150,8 @@ SERVER_PROCESS::SetupListenPort( // user set the env variable but did not give value, let's set it up // pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR); + pEntry->Dereference(); + pEntry = NULL; } } @@ -881,6 +884,7 @@ SERVER_PROCESS::PostStartCheck( m_fReady = TRUE; Finished: + m_fDebuggerAttached = fDebuggerAttached; return hr; } @@ -1066,8 +1070,8 @@ Finished: { if (!fDonePrepareCommandLine) strEventMsg.SafeSnwprintf( - m_struAppFullPath.QueryStr(), ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + m_struAppFullPath.QueryStr(), hr); else strEventMsg.SafeSnwprintf( @@ -1329,10 +1333,10 @@ SERVER_PROCESS::CheckIfServerIsUp( ) { HRESULT hr = S_OK; - DWORD dwResult = 0; + DWORD dwResult = ERROR_INSUFFICIENT_BUFFER; MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL; MIB_TCPROW_OWNER_PID *pOwner = NULL; - DWORD dwSize = 0; + DWORD dwSize = 1000; int iResult = 0; SOCKADDR_IN sockAddr; SOCKET socketCheck = INVALID_SOCKET; @@ -1348,36 +1352,36 @@ SERVER_PROCESS::CheckIfServerIsUp( if (!g_fNsiApiNotSupported) { - dwResult = GetExtendedTcpTable(NULL, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); - - if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + while (dwResult == ERROR_INSUFFICIENT_BUFFER) { - hr = HRESULT_FROM_WIN32(dwResult); - goto Finished; - } + // Increase the buffer size with additional space, MIB_TCPROW 20 bytes + // New entries may be added by other processes before calling GetExtendedTcpTable + dwSize += 200; - pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); - if (pTCPInfo == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } + if (pTCPInfo != NULL) + { + HeapFree(GetProcessHeap(), 0, pTCPInfo); + } - dwResult = GetExtendedTcpTable(pTCPInfo, - &dwSize, - FALSE, - AF_INET, - TCP_TABLE_OWNER_PID_LISTENER, - 0); - if (dwResult != NO_ERROR) - { - hr = HRESULT_FROM_WIN32(dwResult); - goto Finished; + pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); + if (pTCPInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } } // iterate pTcpInfo struct to find PID/PORT entry @@ -1422,6 +1426,12 @@ SERVER_PROCESS::CheckIfServerIsUp( if (iResult == SOCKET_ERROR) { hr = HRESULT_FROM_WIN32(WSAGetLastError()); + if (hr == HRESULT_FROM_WIN32(WSAECONNREFUSED)) + { + // WSAECONNREFUSED means no application listen on the given port. + // This is not a failure. Reset the hresult to S_OK and return fReady to false + hr = S_OK; + } goto Finished; } @@ -1484,7 +1494,7 @@ SERVER_PROCESS::SendSignal( goto Finished; } - if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) + if (WaitForSingleObject(m_hShutdownHandle, m_fDebuggerAttached ? INFINITE : m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); goto Finished; @@ -2006,7 +2016,9 @@ SERVER_PROCESS::~SERVER_PROCESS() InterlockedDecrement(&g_dwActiveServerProcesses); } -VOID +static +VOID +CALLBACK ProcessHandleCallback( _In_ PVOID pContext, _In_ BOOL @@ -2064,12 +2076,36 @@ SERVER_PROCESS::HandleProcessExit() HRESULT hr = S_OK; BOOL fReady = FALSE; DWORD dwProcessId = 0; + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) { CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady); if (!fReady) { + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_SHUTDOWN_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + m_dwProcessId, + m_dwPort))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_PROCESS_SHUTDOWN, + NULL, + 1, + 0, + apsz, + NULL); + } + } m_pProcessManager->ShutdownProcess(this); } @@ -2348,4 +2384,4 @@ SERVER_PROCESS::TerminateBackendProcess( } } } -} \ No newline at end of file +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.cpp new file mode 100644 index 0000000000..abae1963d5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.cpp @@ -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. + +#include "AppOfflineApplication.h" + +#include "HandleWrapper.h" +#include "AppOfflineHandler.h" +#include "exceptions.h" + +HRESULT AppOfflineApplication::CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) +{ + try + { + auto handler = std::make_unique(*pHttpContext, m_strAppOfflineContent); + *pRequestHandler = handler.release(); + } + CATCH_RETURN(); + + return S_OK; +} + +HRESULT AppOfflineApplication::OnAppOfflineFound() +{ + LARGE_INTEGER li = {}; + + HandleWrapper handle = CreateFile(m_appOfflineLocation.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + RETURN_LAST_ERROR_IF(handle == INVALID_HANDLE_VALUE); + + RETURN_LAST_ERROR_IF(!GetFileSizeEx(handle, &li)); + + if (li.HighPart != 0) + { + // > 4gb file size not supported + // todo: log a warning at event log + return E_INVALIDARG; + } + + if (li.LowPart > 0) + { + DWORD bytesRead = 0; + std::string pszBuff(static_cast(li.LowPart) + 1, '\0'); + + RETURN_LAST_ERROR_IF(!ReadFile(handle, pszBuff.data(), li.LowPart, &bytesRead, nullptr)); + pszBuff.resize(bytesRead); + + m_strAppOfflineContent = pszBuff; + } + + return S_OK; +} + +bool AppOfflineApplication::ShouldBeStarted(const IHttpApplication& pApplication) +{ + return FileExists(GetAppOfflineLocation(pApplication)); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.h new file mode 100644 index 0000000000..2c81ae98b1 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineApplication.h @@ -0,0 +1,28 @@ +// 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. + +#pragma once + +#include "application.h" +#include "requesthandler.h" +#include "PollingAppOfflineApplication.h" + +class AppOfflineApplication: public PollingAppOfflineApplication +{ +public: + AppOfflineApplication(const IHttpApplication& pApplication) + : PollingAppOfflineApplication(pApplication, PollingAppOfflineApplicationMode::StopWhenRemoved) + { + CheckAppOffline(); + } + + HRESULT CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler) override; + + HRESULT OnAppOfflineFound() override; + + static bool ShouldBeStarted(const IHttpApplication& pApplication); + +private: + std::string m_strAppOfflineContent; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.cpp new file mode 100644 index 0000000000..e3574d88e8 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.cpp @@ -0,0 +1,30 @@ +// 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. + +#include "AppOfflineHandler.h" + +#include "HandleWrapper.h" + +REQUEST_NOTIFICATION_STATUS AppOfflineHandler::ExecuteRequestHandler() +{ + HTTP_DATA_CHUNK DataChunk {}; + auto pResponse = m_pContext.GetResponse(); + + DBG_ASSERT(pResponse); + + // Ignore failure hresults as nothing we can do + // Set fTrySkipCustomErrors to true as we want client see the offline content + pResponse->SetStatus(503, "Service Unavailable", 0, S_OK, nullptr, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + static_cast(strlen("text/html")), + FALSE + ); + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = m_strAppOfflineContent.data(); + DataChunk.FromMemory.BufferLength = static_cast(m_strAppOfflineContent.size()); + pResponse->WriteEntityChunkByReference(&DataChunk); + + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h new file mode 100644 index 0000000000..87f729bf82 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AppOfflineHandler.h @@ -0,0 +1,24 @@ +// 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. + +#pragma once + +#include +#include "requesthandler.h" + +class AppOfflineHandler: public REQUEST_HANDLER +{ +public: + AppOfflineHandler(IHttpContext& pContext, const std::string appOfflineContent) + : REQUEST_HANDLER(pContext), + m_pContext(pContext), + m_strAppOfflineContent(appOfflineContent) + { + } + + REQUEST_NOTIFICATION_STATUS ExecuteRequestHandler() override; + +private: + IHttpContext& m_pContext; + std::string m_strAppOfflineContent; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h new file mode 100644 index 0000000000..ed9733b68a --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ApplicationFactory.h @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include +#include "iapplication.h" +#include "HandleWrapper.h" + +typedef +HRESULT +(WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)( + _In_ IHttpServer *pServer, + _In_ const IHttpApplication * pHttpApplication, + _In_ APPLICATION_PARAMETER *pParameters, + _In_ DWORD nParameters, + _Out_ IAPPLICATION **pApplication + ); + +class ApplicationFactory +{ +public: + ApplicationFactory(HMODULE hRequestHandlerDll, std::wstring location, PFN_ASPNETCORE_CREATE_APPLICATION pfnAspNetCoreCreateApplication) noexcept: + m_pfnAspNetCoreCreateApplication(pfnAspNetCoreCreateApplication), + m_location(std::move(location)), + m_hRequestHandlerDll(hRequestHandlerDll) + { + } + + HRESULT Execute( + _In_ IHttpServer *pServer, + _In_ IHttpContext *pHttpContext, + _Outptr_ IAPPLICATION **pApplication) const + { + // m_location.data() is const ptr copy to local to get mutable pointer + auto location = m_location; + std::array parameters { + { + {"InProcessExeLocation", location.data()}, + {"TraceContext", pHttpContext->GetTraceContext()} + } + }; + + return m_pfnAspNetCoreCreateApplication(pServer, pHttpContext->GetApplication(), parameters.data(), static_cast(parameters.size()), pApplication); + } + +private: + PFN_ASPNETCORE_CREATE_APPLICATION m_pfnAspNetCoreCreateApplication; + std::wstring m_location; + HandleWrapper m_hRequestHandlerDll; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index 4660054b0b..98c5920ed4 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -1,6 +1,5 @@ - + - Debug @@ -24,7 +23,7 @@ Win32Proj AspNetCoreModule AspNetCore - aspnetcore + aspnetcorev2 false 10.0.15063.0 @@ -56,6 +55,7 @@ Unicode + @@ -76,13 +76,12 @@ - NotUsing Level4 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h $(IntDir)$(TargetName).pch - ..\IISLib;.\Inc + ..\IISLib;.\Inc;..\CommonLib ProgramDatabase MultiThreadedDebug true @@ -96,12 +95,16 @@ true CompileAsCpp true + true + stdcpp17 + stdafx.h Windows true - kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def + UseLinkTimeCodeGeneration ..\Commonlib @@ -109,13 +112,12 @@ - NotUsing Level4 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h $(IntDir)$(TargetName).pch - ..\IISLib;.\Inc + ..\IISLib;.\Inc;..\CommonLib ProgramDatabase MultiThreadedDebug true @@ -128,13 +130,17 @@ false true CompileAsCpp + true true + stdcpp17 + stdafx.h Windows true - kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def + UseLinkTimeCodeGeneration ..\Commonlib @@ -148,8 +154,8 @@ true true WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) - ..\IISLib;inc - precomp.hxx + ..\IISLib;.\Inc;..\CommonLib + stdafx.h MultiThreaded true true @@ -162,14 +168,19 @@ true CompileAsCpp true + true + stdcpp17 + stdafx.h Windows false true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + UseLinkTimeCodeGeneration ..\Commonlib @@ -183,8 +194,8 @@ true true WIN32;NDEBUG;_WINDOWS;_USRDLL;ASPNETCOREMODULE_EXPORTS;%(PreprocessorDefinitions) - precomp.hxx - ..\IISLib;inc + stdafx.h + ..\IISLib;.\Inc;..\CommonLib MultiThreaded true true @@ -197,35 +208,58 @@ true CompileAsCpp true + true + stdcpp17 + stdafx.h Windows false true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + UseLinkTimeCodeGeneration ..\Commonlib - - - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + Create + Create + Create + Create + + @@ -243,7 +277,7 @@ - + PreserveNewest @@ -251,7 +285,18 @@ - + + + + + true + + + true + + + + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp new file mode 100644 index 0000000000..ad2ff17b47 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp @@ -0,0 +1,57 @@ +// 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. + +#include "DisconnectHandler.h" +#include "exceptions.h" +#include "proxymodule.h" +#include "SRWExclusiveLock.h" + +void DisconnectHandler::NotifyDisconnect() +{ + try + { + std::unique_ptr pHandler; + { + SRWExclusiveLock lock(m_handlerLock); + m_pHandler.swap(pHandler); + m_disconnectFired = true; + } + + if (pHandler != nullptr) + { + pHandler->NotifyDisconnect(); + } + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + } +} + +void DisconnectHandler::CleanupStoredContext() noexcept +{ + delete this; +} + +void DisconnectHandler::SetHandler(std::unique_ptr handler) +{ + IREQUEST_HANDLER* pHandler = nullptr; + { + SRWExclusiveLock lock(m_handlerLock); + + handler.swap(m_pHandler); + pHandler = m_pHandler.get(); + } + + assert(pHandler != nullptr); + + if (pHandler != nullptr && (m_disconnectFired || m_pHttpConnection != nullptr && !m_pHttpConnection->IsConnected())) + { + pHandler->NotifyDisconnect(); + } +} + +void DisconnectHandler::RemoveHandler() noexcept +{ + m_pHandler = nullptr; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h new file mode 100644 index 0000000000..36d404edd6 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h @@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include +#include "irequesthandler.h" + +class ASPNET_CORE_PROXY_MODULE; + +class DisconnectHandler final: public IHttpConnectionStoredContext +{ +public: + DisconnectHandler(IHttpConnection* pHttpConnection) + : m_pHandler(nullptr), m_pHttpConnection(pHttpConnection), m_disconnectFired(false) + { + InitializeSRWLock(&m_handlerLock); + } + + virtual + ~DisconnectHandler() + { + RemoveHandler(); + } + + void + NotifyDisconnect() override; + + void + CleanupStoredContext() noexcept override; + + void + SetHandler(std::unique_ptr handler); + + void RemoveHandler() noexcept; + +private: + SRWLOCK m_handlerLock {}; + std::unique_ptr m_pHandler; + IHttpConnection* m_pHttpConnection; + bool m_disconnectFired; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp new file mode 100644 index 0000000000..44210d8b68 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -0,0 +1,326 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "HandlerResolver.h" +#include "exceptions.h" +#include "SRWExclusiveLock.h" +#include "applicationinfo.h" +#include "EventLog.h" +#include "hostfxr_utility.h" +#include "GlobalVersionUtility.h" +#include "HandleWrapper.h" +#include "file_utility.h" +#include "LoggingHelpers.h" +#include "resources.h" +#include "ConfigurationLoadException.h" +#include "WebConfigConfigurationSource.h" +#include "ModuleHelpers.h" +#include "BaseOutputManager.h" +#include "Environment.h" + +const PCWSTR HandlerResolver::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll"; +const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll"; + +HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer) + : m_hModule(hModule), + m_pServer(pServer), + m_loadedApplicationHostingModel(HOSTING_UNKNOWN) +{ + InitializeSRWLock(&m_requestHandlerLoadLock); +} + +HRESULT +HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory) +{ + HRESULT hr = S_OK; + PCWSTR pstrHandlerDllName = nullptr; + bool preventUnload = false; + if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + preventUnload = false; + pstrHandlerDllName = s_pwzAspnetcoreInProcessRequestHandlerName; + } + else + { + // OutOfProcess handler is not able to handle unload correctly + // It has code running after application.Stop exits + preventUnload = true; + pstrHandlerDllName = s_pwzAspnetcoreOutOfProcessRequestHandlerName; + } + HandleWrapper hRequestHandlerDll; + std::wstring location; + std::wstring handlerDllPath; + // Try to see if RH is already loaded, use GetModuleHandleEx to increment ref count + if (!GetModuleHandleEx(0, pstrHandlerDllName, &hRequestHandlerDll)) + { + if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + { + std::unique_ptr options; + std::unique_ptr outputManager; + + RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create( + L"", + pConfiguration.QueryProcessPath(), + pApplication.GetApplicationPhysicalPath(), + pConfiguration.QueryArguments(), + options)); + + location = options->GetDotnetExeLocation(); + + RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider( + pConfiguration.QueryStdoutLogEnabled(), + !m_pServer.IsCommandLineLaunch(), + pConfiguration.QueryStdoutLogFile().c_str(), + pApplication.GetApplicationPhysicalPath(), + outputManager)); + + + hr = FindNativeAssemblyFromHostfxr(*options.get(), pstrHandlerDllName, handlerDllPath, outputManager.get()); + + if (FAILED_LOG(hr)) + { + auto output = outputManager->GetStdOutContent(); + + EventLog::Error( + ASPNETCORE_EVENT_GENERAL_ERROR, + ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG, + output.c_str()); + return hr; + } + } + else + { + if (FAILED_LOG(hr = FindNativeAssemblyFromGlobalLocation(pConfiguration, pstrHandlerDllName, handlerDllPath))) + { + EventLog::Error( + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG, + handlerDllPath.empty() ? s_pwzAspnetcoreOutOfProcessRequestHandlerName : handlerDllPath.c_str()); + + return hr; + } + } + + LOG_INFOF(L"Loading request handler: '%ls'", handlerDllPath.c_str()); + + hRequestHandlerDll = LoadLibrary(handlerDllPath.c_str()); + RETURN_LAST_ERROR_IF_NULL(hRequestHandlerDll); + if (preventUnload) + { + // Pin module in memory + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN, handlerDllPath.c_str(), &hRequestHandlerDll); + } + } + + auto pfnAspNetCoreCreateApplication = ModuleHelpers::GetKnownProcAddress(hRequestHandlerDll, "CreateApplication"); + RETURN_LAST_ERROR_IF_NULL(pfnAspNetCoreCreateApplication); + + pApplicationFactory = std::make_unique(hRequestHandlerDll.release(), location, pfnAspNetCoreCreateApplication); + return S_OK; +} + +HRESULT +HandlerResolver::GetApplicationFactory(const IHttpApplication &pApplication, std::unique_ptr& pApplicationFactory, const ShimOptions& options) +{ + SRWExclusiveLock lock(m_requestHandlerLoadLock); + if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN) + { + // Mixed hosting models + if (m_loadedApplicationHostingModel != options.QueryHostingModel()) + { + EventLog::Error( + ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, + ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, + pApplication.GetApplicationId(), + options.QueryHostingModel()); + + return E_FAIL; + } + // Multiple in-process apps + if (m_loadedApplicationHostingModel == HOSTING_IN_PROCESS && m_loadedApplicationId != pApplication.GetApplicationId()) + { + EventLog::Error( + ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, + ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, + pApplication.GetApplicationId()); + + return E_FAIL; + } + } + + m_loadedApplicationHostingModel = options.QueryHostingModel(); + m_loadedApplicationId = pApplication.GetApplicationId(); + RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, options, pApplicationFactory)); + + return S_OK; +} + +void HandlerResolver::ResetHostingModel() +{ + SRWExclusiveLock lock(m_requestHandlerLoadLock); + + m_loadedApplicationHostingModel = APP_HOSTING_MODEL::HOSTING_UNKNOWN; + m_loadedApplicationId.resize(0); +} + +HRESULT +HandlerResolver::FindNativeAssemblyFromGlobalLocation( + const ShimOptions& pConfiguration, + PCWSTR pstrHandlerDllName, + std::wstring& handlerDllPath +) +{ + try + { + auto handlerPath = Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER"); + if (handlerPath.has_value() && std::filesystem::is_regular_file(handlerPath.value())) + { + handlerDllPath = handlerPath.value(); + return S_OK; + } + + std::wstring modulePath = GlobalVersionUtility::GetModuleName(m_hModule); + + modulePath = GlobalVersionUtility::RemoveFileNameFromFolderPath(modulePath); + + handlerDllPath = GlobalVersionUtility::GetGlobalRequestHandlerPath(modulePath.c_str(), + pConfiguration.QueryHandlerVersion().c_str(), + pstrHandlerDllName + ); + } + catch (...) + { + EventLog::Info( + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG, + pstrHandlerDllName); + + return OBSERVE_CAUGHT_EXCEPTION(); + } + + return S_OK; +} + +// +// Tries to find aspnetcorerh.dll from the application +// Calls into hostfxr.dll to find it. +// Will leave hostfxr.dll loaded as it will be used again to call hostfxr_main. +// +HRESULT +HandlerResolver::FindNativeAssemblyFromHostfxr( + const HOSTFXR_OPTIONS& hostfxrOptions, + PCWSTR libraryName, + std::wstring& handlerDllPath, + BaseOutputManager* outputManager +) +{ + std::wstring struNativeSearchPaths; + size_t intIndex = 0; + size_t intPrevIndex = 0; + DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize; + DWORD dwRequiredBufferSize = 0; + hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = nullptr; + + RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str())); + + try + { + pFnHostFxrSearchDirectories = ModuleHelpers::GetKnownProcAddress(m_hHostFxrDll, "hostfxr_get_native_search_directories"); + } + catch (...) + { + EventLog::Error( + ASPNETCORE_EVENT_GENERAL_ERROR, + ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG, + hostfxrOptions.GetHostFxrLocation().c_str() + ); + return OBSERVE_CAUGHT_EXCEPTION(); + } + + RETURN_LAST_ERROR_IF_NULL(pFnHostFxrSearchDirectories); + struNativeSearchPaths.resize(dwBufferSize); + + outputManager->TryStartRedirection(); + + while (TRUE) + { + DWORD hostfxrArgc; + std::unique_ptr hostfxrArgv; + + hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv); + const auto intHostFxrExitCode = pFnHostFxrSearchDirectories( + hostfxrArgc, + hostfxrArgv.get(), + struNativeSearchPaths.data(), + dwBufferSize, + &dwRequiredBufferSize + ); + + if (intHostFxrExitCode == 0) + { + break; + } + else if (dwRequiredBufferSize > dwBufferSize) + { + dwBufferSize = dwRequiredBufferSize + 1; // for null terminator + + struNativeSearchPaths.resize(dwBufferSize); + } + else + { + // Stop redirecting before logging to event log to avoid logging debug logs + // twice. + outputManager->TryStopRedirection(); + + // If hostfxr didn't set the required buffer size, something in the app is misconfigured + // Ex: Framework not found. + EventLog::Error( + ASPNETCORE_EVENT_GENERAL_ERROR, + ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG + ); + + return E_UNEXPECTED; + } + } + + outputManager->TryStopRedirection(); + + struNativeSearchPaths.resize(struNativeSearchPaths.find(L'\0')); + + auto fFound = FALSE; + + // The native search directories are semicolon delimited. + // Split on semicolons, append aspnetcorerh.dll, and check if the file exists. + while ((intIndex = struNativeSearchPaths.find(L';', intPrevIndex)) != std::wstring::npos) + { + auto path = struNativeSearchPaths.substr(intPrevIndex, intIndex - intPrevIndex); + + if (!path.empty() && !(path[path.length() - 1] == L'\\')) + { + path.append(L"\\"); + } + + path.append(libraryName); + + if (std::filesystem::is_regular_file(path)) + { + handlerDllPath = path; + fFound = TRUE; + break; + } + + intPrevIndex = intIndex + 1; + } + + if (!fFound) + { + EventLog::Error( + ASPNETCORE_EVENT_GENERAL_ERROR, + ASPNETCORE_EVENT_INPROCESS_RH_REFERENCE_MSG, + handlerDllPath.empty() ? s_pwzAspnetcoreInProcessRequestHandlerName : handlerDllPath.c_str()); + return HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND); + } + + return S_OK; +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h new file mode 100644 index 0000000000..1cd9029347 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include "ShimOptions.h" +#include "hostfxroptions.h" +#include "HandleWrapper.h" +#include "ApplicationFactory.h" +#include "BaseOutputManager.h" + +class HandlerResolver +{ +public: + HandlerResolver(HMODULE hModule, const IHttpServer &pServer); + HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::unique_ptr& pApplicationFactory, const ShimOptions& options); + void ResetHostingModel(); + +private: + HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory); + HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath); + HRESULT FindNativeAssemblyFromHostfxr(const HOSTFXR_OPTIONS& hostfxrOptions, PCWSTR libraryName, std::wstring& handlerDllPath, BaseOutputManager* outputManager); + + HMODULE m_hModule; + const IHttpServer &m_pServer; + + SRWLOCK m_requestHandlerLoadLock {}; + std::wstring m_loadedApplicationId; + APP_HOSTING_MODEL m_loadedApplicationHostingModel; + HandleWrapper m_hHostFxrDll; + + static const PCWSTR s_pwzAspnetcoreInProcessRequestHandlerName; + static const PCWSTR s_pwzAspnetcoreOutOfProcessRequestHandlerName; + static const DWORD s_initialGetNativeSearchDirectoriesBufferSize = MAX_PATH * 4; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HtmlResponses.rc b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HtmlResponses.rc new file mode 100644 index 0000000000..d923a2121c Binary files /dev/null and b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/HtmlResponses.rc differ diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/InProcessShimStaticHtml.htm b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/InProcessShimStaticHtml.htm new file mode 100644 index 0000000000..a8c9d7fe1f --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/InProcessShimStaticHtml.htm @@ -0,0 +1,84 @@ + + + + + HTTP Error 500.0 - ANCM In-Process Handler Load Failure + + + +

HTTP Error 500.0 - ANCM In-Process Handler Load Failure

+ +

Common causes of this issue:

+ +
    +
  • The specified version of Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found.
  • +
  • The in process request handler, Microsoft.AspNetCore.Server.IIS, was not referenced in the application.
  • +
  • ANCM could not find dotnet.
  • +
+ +

Troubleshooting steps:

+ +
    +
  • Check the system event log for error messages
  • +
  • Enable logging the application process' stdout messages
  • +
  • Attach a debugger to the application process and inspect
  • +
+ +

+ For more information visit: + %s https://go.microsoft.com/fwlink/?LinkID=2028526 +

+ + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h deleted file mode 100644 index f47b51786b..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationinfo.h +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once -#define API_BUFFER_TOO_SMALL 0x80008098 - -extern BOOL g_fRecycleProcessCalled; - -typedef -HRESULT -(WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)( - _In_ IHttpServer *pServer, - _In_ ASPNETCORE_CONFIG *pConfig, - _Out_ APPLICATION **pApplication - ); - -typedef -HRESULT -(WINAPI * PFN_ASPNETCORE_CREATE_REQUEST_HANDLER)( - _In_ IHttpContext *pHttpContext, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication, - _Out_ REQUEST_HANDLER **pRequestHandler - ); -// -// The key used for hash-table lookups, consists of the port on which the http process is created. -// -class APPLICATION_INFO_KEY -{ -public: - - APPLICATION_INFO_KEY( - VOID - ) : INLINE_STRU_INIT(m_struKey) - { - } - - HRESULT - Initialize( - _In_ LPCWSTR pszKey - ) - { - return m_struKey.Copy(pszKey); - } - - BOOL - GetIsEqual( - const APPLICATION_INFO_KEY * key2 - ) const - { - return m_struKey.Equals(key2->m_struKey); - } - - DWORD CalcKeyHash() const - { - return Hash(m_struKey.QueryStr()); - } - -private: - - INLINE_STRU(m_struKey, 1024); -}; - - -class APPLICATION_INFO -{ -public: - - APPLICATION_INFO(IHttpServer *pServer) : - m_pServer(pServer), - m_cRefs(1), m_fAppOfflineFound(FALSE), - m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL), - m_pConfiguration(NULL), - m_pfnAspNetCoreCreateApplication(NULL), - m_pfnAspNetCoreCreateRequestHandler(NULL) - { - InitializeSRWLock(&m_srwLock); - } - - APPLICATION_INFO_KEY * - QueryApplicationInfoKey() - { - return &m_applicationInfoKey; - } - - virtual - ~APPLICATION_INFO(); - - HRESULT - Initialize( - _In_ ASPNETCORE_CONFIG *pConfiguration, - _In_ FILE_WATCHER *pFileWatcher - ); - - VOID - ReferenceApplicationInfo() const - { - InterlockedIncrement(&m_cRefs); - } - - VOID - DereferenceApplicationInfo() const - { - if (InterlockedDecrement(&m_cRefs) == 0) - { - delete this; - } - } - - APP_OFFLINE_HTM* QueryAppOfflineHtm() - { - return m_pAppOfflineHtm; - } - - BOOL - AppOfflineFound() - { - return m_fAppOfflineFound; - } - - VOID - UpdateAppOfflineFileHandle(); - - HRESULT - StartMonitoringAppOffline(); - - ASPNETCORE_CONFIG* - QueryConfig() - { - return m_pConfiguration; - } - - // - // ExtractApplication will increase the reference counter of the application - // Caller is responsible for dereference the application. - // Otherwise memory leak - // - VOID - ExtractApplication(APPLICATION** ppApplication) - { - AcquireSRWLockShared(&m_srwLock); - if (m_pApplication != NULL) - { - m_pApplication->ReferenceApplication(); - } - *ppApplication = m_pApplication; - ReleaseSRWLockShared(&m_srwLock); - } - - VOID - RecycleApplication(); - - VOID - ShutDownApplication(); - - HRESULT - EnsureApplicationCreated(); - - PFN_ASPNETCORE_CREATE_REQUEST_HANDLER - QueryCreateRequestHandler() - { - return m_pfnAspNetCoreCreateRequestHandler; - } - -private: - HRESULT FindRequestHandlerAssembly(); - HRESULT FindNativeAssemblyFromGlobalLocation(STRU* struFilename); - HRESULT FindNativeAssemblyFromHostfxr(STRU* struFilename); - - static VOID DoRecycleApplication(LPVOID lpParam); - - mutable LONG m_cRefs; - APPLICATION_INFO_KEY m_applicationInfoKey; - BOOL m_fAppOfflineFound; - APP_OFFLINE_HTM *m_pAppOfflineHtm; - FILE_WATCHER_ENTRY *m_pFileWatcherEntry; - ASPNETCORE_CONFIG *m_pConfiguration; - APPLICATION *m_pApplication; - SRWLOCK m_srwLock; - IHttpServer *m_pServer; - PFN_ASPNETCORE_CREATE_APPLICATION m_pfnAspNetCoreCreateApplication; - PFN_ASPNETCORE_CREATE_REQUEST_HANDLER m_pfnAspNetCoreCreateRequestHandler; -}; - -class APPLICATION_INFO_HASH : - public HASH_TABLE -{ - -public: - - APPLICATION_INFO_HASH() - {} - - APPLICATION_INFO_KEY * - ExtractKey( - APPLICATION_INFO *pApplicationInfo - ) - { - return pApplicationInfo->QueryApplicationInfoKey(); - } - - DWORD - CalcKeyHash( - APPLICATION_INFO_KEY *key - ) - { - return key->CalcKeyHash(); - } - - BOOL - EqualKeys( - APPLICATION_INFO_KEY *key1, - APPLICATION_INFO_KEY *key2 - ) - { - return key1->GetIsEqual(key2); - } - - VOID - ReferenceRecord( - APPLICATION_INFO *pApplicationInfo - ) - { - pApplicationInfo->ReferenceApplicationInfo(); - } - - VOID - DereferenceRecord( - APPLICATION_INFO *pApplicationInfo - ) - { - pApplicationInfo->DereferenceApplicationInfo(); - } - - static - VOID - ReferenceCopyToTable( - APPLICATION_INFO * pEntry, - PVOID pvData - ) - { - APPLICATION_INFO_HASH *pHash = static_cast(pvData); - DBG_ASSERT(pHash); - pHash->InsertRecord(pEntry); - } - -private: - - APPLICATION_INFO_HASH(const APPLICATION_INFO_HASH &); - void operator=(const APPLICATION_INFO_HASH &); -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h deleted file mode 100644 index 7201d7de49..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/applicationmanager.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#define DEFAULT_HASH_BUCKETS 17 - -// -// This class will manage the lifecycle of all Asp.Net Core applciation -// It should be global singleton. -// Should always call GetInstance to get the object instance -// - -struct CONFIG_CHANGE_CONTEXT -{ - PCWSTR pstrPath; - MULTISZ MultiSz; -}; - -class APPLICATION_MANAGER -{ -public: - - static - APPLICATION_MANAGER* - GetInstance( - VOID - ) - { - if ( sm_pApplicationManager == NULL ) - { - sm_pApplicationManager = new APPLICATION_MANAGER(); - } - - return sm_pApplicationManager; - } - - static - VOID - Cleanup( - VOID - ) - { - if(sm_pApplicationManager != NULL) - { - delete sm_pApplicationManager; - sm_pApplicationManager = NULL; - } - } - - static - BOOL - FindConfigChangedApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext - ); - - static - VOID - ShutdownApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext - ); - - HRESULT - GetOrCreateApplicationInfo( - _In_ IHttpServer* pServer, - _In_ ASPNETCORE_CONFIG* pConfig, - _Out_ APPLICATION_INFO ** ppApplicationInfo - ); - - HRESULT - RecycleApplicationFromManager( - _In_ LPCWSTR pszApplicationId - ); - - VOID - ShutDown(); - - ~APPLICATION_MANAGER() - { - if (m_pFileWatcher != NULL) - { - delete m_pFileWatcher; - m_pFileWatcher = NULL; - } - - if(m_pApplicationInfoHash != NULL) - { - m_pApplicationInfoHash->Clear(); - delete m_pApplicationInfoHash; - m_pApplicationInfoHash = NULL; - } - } - - FILE_WATCHER* - GetFileWatcher() - { - return m_pFileWatcher; - } - - HRESULT Initialize() - { - HRESULT hr = S_OK; - - if(m_pApplicationInfoHash == NULL) - { - m_pApplicationInfoHash = new APPLICATION_INFO_HASH(); - if(m_pApplicationInfoHash == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = m_pApplicationInfoHash->Initialize(DEFAULT_HASH_BUCKETS); - if(FAILED(hr)) - { - goto Finished; - } - } - - if( m_pFileWatcher == NULL ) - { - m_pFileWatcher = new FILE_WATCHER; - if(m_pFileWatcher == NULL) - { - hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); - goto Finished; - } - - m_pFileWatcher->Create(); - } - - Finished: - return hr; - } - -private: - // - // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload - // - APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL), - m_pFileWatcher(NULL), - m_hostingModel(HOSTING_UNKNOWN) - { - InitializeSRWLock(&m_srwLock); - } - - FILE_WATCHER *m_pFileWatcher; - APPLICATION_INFO_HASH *m_pApplicationInfoHash; - static APPLICATION_MANAGER *sm_pApplicationManager; - SRWLOCK m_srwLock; - APP_HOSTING_MODEL m_hostingModel; -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h deleted file mode 100644 index 85b6c13ea8..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/appoffline.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once -class APP_OFFLINE_HTM -{ -public: - APP_OFFLINE_HTM(LPCWSTR pszPath) : m_cRefs(1) - { - m_Path.Copy(pszPath); - } - - VOID - ReferenceAppOfflineHtm() const - { - InterlockedIncrement(&m_cRefs); - } - - VOID - DereferenceAppOfflineHtm() const - { - if (InterlockedDecrement(&m_cRefs) == 0) - { - delete this; - } - } - - BOOL - Load( - VOID - ) - { - BOOL fResult = TRUE; - LARGE_INTEGER li = { 0 }; - CHAR *pszBuff = NULL; - HANDLE handle = INVALID_HANDLE_VALUE; - - handle = CreateFile(m_Path.QueryStr(), - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (handle == INVALID_HANDLE_VALUE) - { - if (HRESULT_FROM_WIN32(GetLastError()) == ERROR_FILE_NOT_FOUND) - { - fResult = FALSE; - } - - // This Load() member function is supposed be called only when the change notification event of file creation or file modification happens. - // If file is currenlty locked exclusively by other processes, we might get INVALID_HANDLE_VALUE even though the file exists. In that case, we should return TRUE here. - goto Finished; - } - - if (!GetFileSizeEx(handle, &li)) - { - goto Finished; - } - - if (li.HighPart != 0) - { - // > 4gb file size not supported - // todo: log a warning at event log - goto Finished; - } - - DWORD bytesRead = 0; - - if (li.LowPart > 0) - { - pszBuff = new CHAR[li.LowPart + 1]; - - if (ReadFile(handle, pszBuff, li.LowPart, &bytesRead, NULL)) - { - m_Contents.Copy(pszBuff, bytesRead); - } - } - - Finished: - if (handle != INVALID_HANDLE_VALUE) - { - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - } - - if (pszBuff != NULL) - { - delete[] pszBuff; - pszBuff = NULL; - } - - return fResult; - } - - mutable LONG m_cRefs; - STRA m_Contents; - STRU m_Path; -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h deleted file mode 100644 index d8bde448a7..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/filewatcher.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) -#define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 -#ifndef CONTAINING_RECORD -// -// Calculate the address of the base of the structure given its type, and an -// address of a field within the structure. -// - -#define CONTAINING_RECORD(address, type, field) \ - ((type *)((PCHAR)(address)-(ULONG_PTR)(&((type *)0)->field))) - -#endif // !CONTAINING_RECORD -#define FILE_NOTIFY_VALID_MASK 0x00000fff -#define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') -#define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') - -class APPLICATION_INFO; - -class FILE_WATCHER{ -public: - - FILE_WATCHER(); - - ~FILE_WATCHER(); - - HRESULT Create(); - - HANDLE - QueryCompletionPort( - VOID - ) const - { - return m_hCompletionPort; - } - - static - DWORD - WINAPI ChangeNotificationThread(LPVOID); - - static - void - WINAPI FileWatcherCompletionRoutine - ( - DWORD dwCompletionStatus, - DWORD cbCompletion, - OVERLAPPED * pOverlapped - ); - -private: - HANDLE m_hCompletionPort; - HANDLE m_hChangeNotificationThread; - volatile BOOL m_fThreadExit; -}; - -class FILE_WATCHER_ENTRY -{ -public: - FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); - - OVERLAPPED _overlapped; - - HRESULT - Create( - _In_ PCWSTR pszDirectoryToMonitor, - _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION_INFO* pApplicationInfo, - _In_ HANDLE hImpersonationToken - ); - - VOID - ReferenceFileWatcherEntry() const - { - InterlockedIncrement(&_cRefs); - } - - VOID - DereferenceFileWatcherEntry() const - { - if (InterlockedDecrement(&_cRefs) == 0) - { - delete this; - } - } - - BOOL - QueryIsValid() const - { - return _fIsValid; - } - - VOID - MarkEntryInValid() - { - _fIsValid = FALSE; - } - - HRESULT Monitor(); - - VOID StopMonitor(); - - HRESULT - HandleChangeCompletion( - _In_ DWORD dwCompletionStatus, - _In_ DWORD cbCompletion - ); - -private: - virtual ~FILE_WATCHER_ENTRY(); - - DWORD _dwSignature; - BUFFER _buffDirectoryChanges; - HANDLE _hImpersonationToken; - HANDLE _hDirectory; - FILE_WATCHER* _pFileMonitor; - APPLICATION_INFO* _pApplicationInfo; - STRU _strFileName; - STRU _strDirectoryName; - LONG _lStopMonitorCalled; - mutable LONG _cRefs; - BOOL _fIsValid; - SRWLOCK _srwLock; -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/fx_ver.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/fx_ver.h deleted file mode 100644 index f485ba5a6e..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/fx_ver.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#ifndef __FX_VER_H__ -#define __FX_VER_H__ -#include - -// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not -// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 -struct fx_ver_t -{ - fx_ver_t(int major, int minor, int patch); - fx_ver_t(int major, int minor, int patch, const std::wstring& pre); - fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); - - int get_major() const { return m_major; } - int get_minor() const { return m_minor; } - int get_patch() const { return m_patch; } - - void set_major(int m) { m_major = m; } - void set_minor(int m) { m_minor = m; } - void set_patch(int p) { m_patch = p; } - - bool is_prerelease() const { return !m_pre.empty(); } - - std::wstring as_str() const; - std::wstring prerelease_glob() const; - std::wstring patch_glob() const; - - bool operator ==(const fx_ver_t& b) const; - bool operator !=(const fx_ver_t& b) const; - bool operator <(const fx_ver_t& b) const; - bool operator >(const fx_ver_t& b) const; - bool operator <=(const fx_ver_t& b) const; - bool operator >=(const fx_ver_t& b) const; - - static bool parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production = false); - -private: - int m_major; - int m_minor; - int m_patch; - std::wstring m_pre; - std::wstring m_build; - - static int compare(const fx_ver_t&a, const fx_ver_t& b); -}; - -#endif // __FX_VER_H__ \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h deleted file mode 100644 index 7e5f30a8eb..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/proxymodule.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -extern HTTP_MODULE_ID g_pModuleId; -extern IHttpServer *g_pHttpServer; -extern HMODULE g_hAspnetCoreRH; - -class ASPNET_CORE_PROXY_MODULE : public CHttpModule -{ - public: - - ASPNET_CORE_PROXY_MODULE(); - - ~ASPNET_CORE_PROXY_MODULE(); - - void * operator new(size_t size, IModuleAllocator * pPlacement) - { - return pPlacement->AllocateMemory(static_cast(size)); - } - - VOID - operator delete( - void * - ) - {} - - __override - REQUEST_NOTIFICATION_STATUS - OnExecuteRequestHandler( - IHttpContext * pHttpContext, - IHttpEventProvider * pProvider - ); - - __override - REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( - IHttpContext * pHttpContext, - DWORD dwNotification, - BOOL fPostNotification, - IHttpEventProvider * pProvider, - IHttpCompletionInfo * pCompletionInfo - ); - - private: - - APPLICATION_INFO *m_pApplicationInfo; - APPLICATION *m_pApplication; - REQUEST_HANDLER *m_pHandler; -}; - -class ASPNET_CORE_PROXY_MODULE_FACTORY : public IHttpModuleFactory -{ - public: - HRESULT - GetHttpModule( - CHttpModule ** ppModule, - IModuleAllocator * pAllocator - ); - - VOID - Terminate(); -}; \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/OutOfProcessShimStaticHtml.htm b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/OutOfProcessShimStaticHtml.htm new file mode 100644 index 0000000000..1194cf4025 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/OutOfProcessShimStaticHtml.htm @@ -0,0 +1,81 @@ + + + + + HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure + + + +

HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure

+ +

Common causes of this issue:

+
    +
  • The out of process request handler, aspnetcorev2_outofprocess.dll, could not be found next to the aspnetcorev2.dll.
  • +
  • Could not read configuration correctly. Check the application's associated web.config.
  • +
+ +

Troubleshooting steps:

+
    +
  • Check the system event log for error messages
  • +
  • Enable logging the application process' stdout messages
  • +
  • Attach a debugger to the application process and inspect
  • +
+ +

+ For more information visit: + %s https://go.microsoft.com/fwlink/?LinkID=2028526 +

+ + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.cpp new file mode 100644 index 0000000000..cf2395e7b5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.cpp @@ -0,0 +1,61 @@ +// 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. + +#include "PollingAppOfflineApplication.h" + +#include +#include "SRWExclusiveLock.h" +#include "HandleWrapper.h" +#include "exceptions.h" + +HRESULT PollingAppOfflineApplication::TryCreateHandler(_In_ IHttpContext* pHttpContext, _Outptr_result_maybenull_ IREQUEST_HANDLER** pRequestHandler) +{ + CheckAppOffline(); + return LOG_IF_FAILED(APPLICATION::TryCreateHandler(pHttpContext, pRequestHandler)); +} + +void +PollingAppOfflineApplication::CheckAppOffline() +{ + if (m_fStopCalled) + { + return; + } + + const auto ulCurrentTime = GetTickCount64(); + // + // we only care about app offline presented. If not, it means the application has started + // and is monitoring the app offline file + // we cache the file exist check result for 200 ms + // + if (ulCurrentTime - m_ulLastCheckTime > c_appOfflineRefreshIntervalMS) + { + SRWExclusiveLock lock(m_statusLock); + if (ulCurrentTime - m_ulLastCheckTime > c_appOfflineRefreshIntervalMS) + { + m_fAppOfflineFound = FileExists(m_appOfflineLocation); + if(m_fAppOfflineFound) + { + LOG_IF_FAILED(OnAppOfflineFound()); + } + m_ulLastCheckTime = ulCurrentTime; + } + } + + if (m_fAppOfflineFound != (m_mode == StopWhenRemoved)) + { + Stop(/* fServerInitiated */ false); + } +} + + +std::filesystem::path PollingAppOfflineApplication::GetAppOfflineLocation(const IHttpApplication& pApplication) +{ + return std::filesystem::path(pApplication.GetApplicationPhysicalPath()) / "app_offline.htm"; +} + +bool PollingAppOfflineApplication::FileExists(const std::filesystem::path& path) noexcept +{ + std::error_code ec; + return is_regular_file(path, ec) || ec.value() == ERROR_SHARING_VIOLATION; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h new file mode 100644 index 0000000000..0f0eb58c8c --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once +#include +#include "application.h" + +enum PollingAppOfflineApplicationMode +{ + StopWhenAdded, + StopWhenRemoved +}; + +class PollingAppOfflineApplication: public APPLICATION +{ +public: + PollingAppOfflineApplication(const IHttpApplication& pApplication, PollingAppOfflineApplicationMode mode) + : APPLICATION(pApplication), + m_ulLastCheckTime(0), + m_appOfflineLocation(GetAppOfflineLocation(pApplication)), + m_fAppOfflineFound(false), + m_mode(mode) + { + InitializeSRWLock(&m_statusLock); + } + + HRESULT + TryCreateHandler( + _In_ IHttpContext *pHttpContext, + _Outptr_result_maybenull_ IREQUEST_HANDLER **pRequestHandler) override; + + void CheckAppOffline(); + virtual HRESULT OnAppOfflineFound() = 0; + void StopInternal(bool fServerInitiated) override { UNREFERENCED_PARAMETER(fServerInitiated); } + +protected: + std::filesystem::path m_appOfflineLocation; + static std::filesystem::path GetAppOfflineLocation(const IHttpApplication& pApplication); + static bool FileExists(const std::filesystem::path& path) noexcept; +private: + static const int c_appOfflineRefreshIntervalMS = 200; + std::string m_strAppOfflineContent; + ULONGLONG m_ulLastCheckTime; + bool m_fAppOfflineFound; + SRWLOCK m_statusLock {}; + PollingAppOfflineApplicationMode m_mode; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h new file mode 100644 index 0000000000..b5b86aa20c --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ServerErrorApplication.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "PollingAppOfflineApplication.h" +#include "requesthandler.h" +#include "ServerErrorHandler.h" + +class ServerErrorApplication : public PollingAppOfflineApplication +{ +public: + ServerErrorApplication(const IHttpApplication& pApplication, HRESULT hr, HINSTANCE moduleInstance) + : ServerErrorApplication(pApplication, hr, moduleInstance, true /* disableStartupPage*/, 0 /* page */) + { + } + + ServerErrorApplication(const IHttpApplication& pApplication, HRESULT hr, HINSTANCE moduleInstance, bool disableStartupPage, int page) + : m_HR(hr), + m_disableStartupPage(disableStartupPage), + m_page(page), + m_moduleInstance(moduleInstance), + PollingAppOfflineApplication(pApplication, PollingAppOfflineApplicationMode::StopWhenAdded) + { + } + + ~ServerErrorApplication() = default; + + HRESULT CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override + { + auto handler = std::make_unique(*pHttpContext, 500ui16, 0ui16, "Internal Server Error", m_HR, m_moduleInstance, m_disableStartupPage, m_page); + *pRequestHandler = handler.release(); + return S_OK; + } + + HRESULT OnAppOfflineFound() noexcept override { return S_OK; } +private: + HRESULT m_HR; + bool m_disableStartupPage; + int m_page; + HINSTANCE m_moduleInstance; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp new file mode 100644 index 0000000000..cb7f1488d7 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ShimOptions.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +#define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" + +ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : + m_hostingModel(HOSTING_UNKNOWN), + m_fStdoutLogEnabled(false) +{ + auto const section = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); + auto hostingModel = section->GetString(CS_ASPNETCORE_HOSTING_MODEL).value_or(L""); + + if (hostingModel.empty() || equals_ignore_case(hostingModel, CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS)) + { + m_hostingModel = HOSTING_OUT_PROCESS; + } + else if (equals_ignore_case(hostingModel, CS_ASPNETCORE_HOSTING_MODEL_INPROCESS)) + { + m_hostingModel = HOSTING_IN_PROCESS; + } + else + { + throw ConfigurationLoadException(format( + L"Unknown hosting model '%s'. Please specify either hostingModel=\"inprocess\" " + "or hostingModel=\"outofprocess\" in the web.config file.", hostingModel.c_str())); + } + + if (m_hostingModel == HOSTING_OUT_PROCESS) + { + const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS); + m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring()); + } + + m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); + m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); + m_struStdoutLogFile = section->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); + m_fDisableStartupPage = section->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h new file mode 100644 index 0000000000..3cfb47169e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "ConfigurationSource.h" +#include "exceptions.h" + +enum APP_HOSTING_MODEL +{ + HOSTING_UNKNOWN = 0, + HOSTING_IN_PROCESS, + HOSTING_OUT_PROCESS +}; + +class ShimOptions: NonCopyable +{ +public: + const std::wstring& + QueryProcessPath() const noexcept + { + return m_strProcessPath; + } + + const std::wstring& + QueryArguments() const noexcept + { + return m_strArguments; + } + + APP_HOSTING_MODEL + QueryHostingModel() const noexcept + { + return m_hostingModel; + } + + const std::wstring& + QueryHandlerVersion() const noexcept + { + return m_strHandlerVersion; + } + + BOOL + QueryStdoutLogEnabled() const noexcept + { + return m_fStdoutLogEnabled; + } + + const std::wstring& + QueryStdoutLogFile() const noexcept + { + return m_struStdoutLogFile; + } + + bool + QueryDisableStartupPage() const noexcept + { + return m_fDisableStartupPage; + } + + ShimOptions(const ConfigurationSource &configurationSource); + +private: + std::wstring m_strArguments; + std::wstring m_strProcessPath; + APP_HOSTING_MODEL m_hostingModel; + std::wstring m_strHandlerVersion; + std::wstring m_struStdoutLogFile; + bool m_fStdoutLogEnabled; + bool m_fDisableStartupPage; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Source.def b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Source.def index 9aae10ab5d..74135f4835 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Source.def +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Source.def @@ -1,4 +1,4 @@ -LIBRARY aspnetcore +LIBRARY aspnetcorev2 EXPORTS RegisterModule diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ancm.mof b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ancm.mof index 5a50b19914..359806f8a2 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ancm.mof +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/ancm.mof @@ -5,7 +5,7 @@ /* * AspNetCore module trace events layout * Uncomment the following class to run mof2trace to generate header file - * comment it back before checking it in */ + * comment it back before checking it in [Dynamic, Description("IIS: WWW Server"), Guid("{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}"), @@ -186,12 +186,28 @@ class ANCMWinHttpCallBack:ANCM_Events }; [Dynamic, - Description("Inprocess executing request failure") : amended, + Description("Starting inprocess execute request") : amended, EventType(7), - EventLevel(2), - EventTypeName("ANCM_EXECUTE_REQUEST_FAIL") : amended + EventLevel(4), + EventTypeName("ANCM_INPROC_EXECUTE_REQUEST_START") : amended ] -class ANCMExecuteFailed:ANCM_Events +class ANCMExecuteStart:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; +}; + +[Dynamic, + Description("Ending inprocess execute request") : amended, + EventType(8), + EventLevel(5), + EventTypeName("ANCM_INPROC_EXECUTE_REQUEST_COMPLETION") : amended +] +class ANCMExecuteEnd:ANCM_Events { [WmiDataId(1), Description("Context ID") : amended, @@ -200,9 +216,156 @@ class ANCMExecuteFailed:ANCM_Events read] object ContextId; [WmiDataId(2), - Description("InternetStatus") : amended, - format("x"), + Description("Notification status") : amended, + format("d"), read] - uint32 ErrorCode; + uint32 requestStatus; +}; + +[Dynamic, + Description("Starting inprocess async completion") : amended, + EventType(9), + EventLevel(5), + EventTypeName("ANCM_INPROC_ASYNC_COMPLETION_START") : amended +] +class ANCMAsyncStart:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; +}; + +[Dynamic, + Description("Ending inprocess async completion") : amended, + EventType(10), + EventLevel(5), + EventTypeName("ANCM_INPROC_ASYNC_COMPLETION_COMPLETION") : amended +] +class ANCMAsyncEnd:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; + [WmiDataId(2), + Description("Notification status") : amended, + format("d"), + read] + uint32 requestStatus; +}; + +[Dynamic, + Description("Inprocess app shutdown") : amended, + EventType(11), + EventLevel(4), + EventTypeName("ANCM_INPROC_REQUEST_SHUTDOWN") : amended +] +class ANCMRequestShutdown:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; +}; + +[Dynamic, + Description("Inprocess request disconnect") : amended, + EventType(12), + EventLevel(4), + EventTypeName("ANCM_INPROC_REQUEST_DISCONNECT") : amended +] +class ANCMRequestDisconnect:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; +}; + +[Dynamic, + Description("Indicate managed request complete") : amended, + EventType(13), + EventLevel(4), + EventTypeName("ANCM_INPROC_MANAGED_REQUEST_COMPLETION") : amended +] +class ANCMManagedRequestCompletion:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; +}; + +[Dynamic, + Description("Failed HRESULT") : amended, + EventType(14), + EventLevel(3), + EventTypeName("ANCM_HRESULT_FAILED") : amended +] +class ANCMHRESULTFailed:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; + [WmiDataId(2), + Description("Failed source file location") : amended, + StringTermination("NullTerminated"), + read] + string File; + [WmiDataId(3), + Description("Failed line number") : amended, + format("d"), + read] + uint32 Line; + [WmiDataId(4), + Description("HResult") : amended, + format("x"), + read] + uint32 HResult; +}; + +[Dynamic, + Description("Caught exception") : amended, + EventType(15), + EventLevel(3), + EventTypeName("ANCM_EXCEPTION_CAUGHT") : amended +] +class ANCMExceptionCaughFailed:ANCM_Events +{ + [WmiDataId(1), + Description("Context ID") : amended, + extension("Guid"), + ActivityID, + read] + object ContextId; + [WmiDataId(2), + Description("Exception catch file location") : amended, + StringTermination("NullTerminated"), + read] + string File; + [WmiDataId(3), + Description("Exception catch line number") : amended, + format("d"), + read] + uint32 Line; + [WmiDataId(2), + Description("Exception description") : amended, + StringTermination("NullTerminated"), + read] + string Description; }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp new file mode 100644 index 0000000000..36b0c2d902 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -0,0 +1,182 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "applicationinfo.h" + +#include "proxymodule.h" +#include "hostfxr_utility.h" +#include "debugutil.h" +#include "resources.h" +#include "SRWExclusiveLock.h" +#include "exceptions.h" +#include "EventLog.h" +#include "ServerErrorApplication.h" +#include "AppOfflineApplication.h" +#include "WebConfigConfigurationSource.h" +#include "ConfigurationLoadException.h" +#include "resource.h" + +extern HINSTANCE g_hServerModule; + +HRESULT +APPLICATION_INFO::CreateHandler( + IHttpContext& pHttpContext, + std::unique_ptr& pHandler +) +{ + HRESULT hr = S_OK; + + { + SRWSharedLock lock(m_applicationLock); + + RETURN_IF_FAILED(hr = TryCreateHandler(pHttpContext, pHandler)); + + if (hr == S_OK) + { + return S_OK; + } + } + + { + SRWExclusiveLock lock(m_applicationLock); + + // check if other thread created application + RETURN_IF_FAILED(hr = TryCreateHandler(pHttpContext, pHandler)); + + // In some cases (adding and removing app_offline quickly) application might start and stop immediately + // so retry until we get valid handler or error + while (hr != S_OK) + { + // At this point application is either null or shutdown and is returning S_FALSE + + if (m_pApplication != nullptr) + { + LOG_INFO(L"Application went offline"); + + // Call to wait for application to complete stopping + m_pApplication->Stop(/* fServerInitiated */ false); + m_pApplication = nullptr; + m_pApplicationFactory = nullptr; + } + + RETURN_IF_FAILED(CreateApplication(pHttpContext)); + + RETURN_IF_FAILED(hr = TryCreateHandler(pHttpContext, pHandler)); + } + } + + return S_OK; +} + +HRESULT +APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext) +{ + auto& pHttpApplication = *pHttpContext.GetApplication(); + if (AppOfflineApplication::ShouldBeStarted(pHttpApplication)) + { + LOG_INFO(L"Detected app_offline file, creating polling application"); + m_pApplication = make_application(pHttpApplication); + + return S_OK; + } + else + { + try + { + const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication); + ShimOptions options(configurationSource); + + const auto hr = TryCreateApplication(pHttpContext, options); + + if (FAILED_LOG(hr)) + { + // Log the failure and update application info to not try again + EventLog::Error( + ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, + ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, + pHttpApplication.GetApplicationId(), + hr); + + m_pApplication = make_application( + pHttpApplication, + hr, + g_hServerModule, + options.QueryDisableStartupPage(), + options.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS ? IN_PROCESS_SHIM_STATIC_HTML : OUT_OF_PROCESS_SHIM_STATIC_HTML); + } + return S_OK; + } + catch (ConfigurationLoadException &ex) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + ex.get_message().c_str()); + } + catch (...) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + L""); + } + + m_pApplication = make_application( + pHttpApplication, + E_FAIL, + g_hServerModule); + + return S_OK; + } +} + +HRESULT +APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options) +{ + RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options)); + LOG_INFO(L"Creating handler application"); + + IAPPLICATION * newApplication; + RETURN_IF_FAILED(m_pApplicationFactory->Execute( + &m_pServer, + &pHttpContext, + &newApplication)); + + m_pApplication.reset(newApplication); + return S_OK; +} + +HRESULT +APPLICATION_INFO::TryCreateHandler( + IHttpContext& pHttpContext, + std::unique_ptr& pHandler) +{ + if (m_pApplication != nullptr) + { + IREQUEST_HANDLER * newHandler; + const auto result = m_pApplication->TryCreateHandler(&pHttpContext, &newHandler); + RETURN_IF_FAILED(result); + + if (result == S_OK) + { + pHandler.reset(newHandler); + // another thread created the application + return S_OK; + } + } + return S_FALSE; +} + +VOID +APPLICATION_INFO::ShutDownApplication(bool fServerInitiated) +{ + SRWExclusiveLock lock(m_applicationLock); + + if (m_pApplication) + { + LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); + m_pApplication->Stop(fServerInitiated); + m_pApplication = nullptr; + m_pApplicationFactory = nullptr; + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h new file mode 100644 index 0000000000..b40e0a8ba9 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "hostfxroptions.h" +#include "iapplication.h" +#include "SRWSharedLock.h" +#include "HandlerResolver.h" + +#define API_BUFFER_TOO_SMALL 0x80008098 + +extern BOOL g_fRecycleProcessCalled; + +class APPLICATION_INFO: NonCopyable +{ +public: + + APPLICATION_INFO( + IHttpServer &pServer, + IHttpApplication &pApplication, + HandlerResolver &pHandlerResolver + ) : + m_pServer(pServer), + m_handlerResolver(pHandlerResolver), + m_strConfigPath(pApplication.GetAppConfigPath()), + m_strInfoKey(pApplication.GetApplicationId()) + { + InitializeSRWLock(&m_applicationLock); + } + + ~APPLICATION_INFO() = default; + + const std::wstring& + QueryApplicationInfoKey() noexcept + { + return m_strInfoKey; + } + + const std::wstring& + QueryConfigPath() noexcept + { + return m_strConfigPath; + } + + VOID + ShutDownApplication(bool fServerInitiated); + + HRESULT + CreateHandler( + IHttpContext& pHttpContext, + std::unique_ptr& pHandler); + + bool ConfigurationPathApplies(const std::wstring& path) + { + // We need to check that the last character of the config path + // is either a null terminator or a slash. + // This checks the case where the config path was + // MACHINE/WEBROOT/site and your site path is MACHINE/WEBROOT/siteTest + auto const changed = m_strConfigPath._Starts_with(path); + if (changed) + { + const auto lastChar = m_strConfigPath[m_strConfigPath.length()]; + return lastChar == L'\0' || lastChar == L'/'; + } + return false; + } + +private: + + HRESULT + TryCreateHandler( + IHttpContext& pHttpContext, + std::unique_ptr& pHandler); + + HRESULT + CreateApplication(IHttpContext& pHttpContext); + + HRESULT + TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options); + + IHttpServer &m_pServer; + HandlerResolver &m_handlerResolver; + + std::wstring m_strConfigPath; + std::wstring m_strInfoKey; + SRWLOCK m_applicationLock {}; + + std::unique_ptr m_pApplicationFactory; + std::unique_ptr m_pApplication; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp new file mode 100644 index 0000000000..d1e2b12375 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -0,0 +1,183 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "applicationmanager.h" + +#include "proxymodule.h" +#include "resources.h" +#include "SRWExclusiveLock.h" +#include "exceptions.h" +#include "EventLog.h" + +extern BOOL g_fInShutdown; + +// +// Retrieves the application info from the application manager +// Will create the application info if it isn't initialized +// +HRESULT +APPLICATION_MANAGER::GetOrCreateApplicationInfo( + _In_ IHttpContext& pHttpContext, + _Out_ std::shared_ptr& ppApplicationInfo +) +{ + auto &pApplication = *pHttpContext.GetApplication(); + + // The configuration path is unique for each application and is used for the + // key in the applicationInfoHash. + std::wstring pszApplicationId = pApplication.GetApplicationId(); + + { + // When accessing the m_pApplicationInfoHash, we need to acquire the application manager + // lock to avoid races on setting state. + SRWSharedLock readLock(m_srwLock); + + if (g_fInShutdown) + { + return HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); + } + + const auto pair = m_pApplicationInfoHash.find(pszApplicationId); + if (pair != m_pApplicationInfoHash.end()) + { + ppApplicationInfo = pair->second; + return S_OK; + } + + // It's important to release read lock here so exclusive lock + // can be reacquired later as SRW lock doesn't allow upgrades + } + + // Take exclusive lock before creating the application + SRWExclusiveLock writeLock(m_srwLock); + + if (!m_fDebugInitialize) + { + DebugInitializeFromConfig(m_pHttpServer, pApplication); + m_fDebugInitialize = TRUE; + } + + // Check if other thread created the application + const auto pair = m_pApplicationInfoHash.find(pszApplicationId); + if (pair != m_pApplicationInfoHash.end()) + { + ppApplicationInfo = pair->second; + return S_OK; + } + + ppApplicationInfo = std::make_shared(m_pHttpServer, pApplication, m_handlerResolver); + m_pApplicationInfoHash.emplace(pszApplicationId, ppApplicationInfo); + + return S_OK; +} + +// +// Finds any applications affected by a configuration change and calls Recycle on them +// InProcess: Triggers g_httpServer->RecycleProcess() and keep the application inside of the manager. +// This will cause a shutdown event to occur through the global stop listening event. +// OutOfProcess: Removes all applications in the application manager and calls Recycle, which will call Shutdown, +// on each application. +// +HRESULT +APPLICATION_MANAGER::RecycleApplicationFromManager( + _In_ LPCWSTR pszApplicationId +) +{ + try + { + std::vector> applicationsToRecycle; + + if (g_fInShutdown) + { + // We are already shutting down, ignore this event as a global configuration change event + // can occur after global stop listening for some reason. + return S_OK; + } + + { + SRWExclusiveLock lock(m_srwLock); + if (g_fInShutdown) + { + return S_OK; + } + const std::wstring configurationPath = pszApplicationId; + + auto itr = m_pApplicationInfoHash.begin(); + while (itr != m_pApplicationInfoHash.end()) + { + if (itr->second->ConfigurationPathApplies(configurationPath)) + { + applicationsToRecycle.emplace_back(itr->second); + itr = m_pApplicationInfoHash.erase(itr); + } + else + { + ++itr; + } + } + + // All applications were unloaded reset handler resolver validation logic + if (m_pApplicationInfoHash.empty()) + { + m_handlerResolver.ResetHostingModel(); + } + } + + // If we receive a request at this point. + // OutOfProcess: we will create a new application with new configuration + // InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess + // on the worker proocess + + if (!applicationsToRecycle.empty()) + { + for (auto& application : applicationsToRecycle) + { + try + { + application->ShutDownApplication(/* fServerInitiated */ false); + } + catch (...) + { + LOG_ERRORF(L"Failed to stop application '%ls'", application->QueryApplicationInfoKey().c_str()); + OBSERVE_CAUGHT_EXCEPTION(); + + // Failed to recycle an application. Log an event + EventLog::Error( + ASPNETCORE_EVENT_RECYCLE_APP_FAILURE, + ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG, + pszApplicationId); + // Need to recycle the process as we cannot recycle the application + if (!g_fRecycleProcessCalled) + { + g_fRecycleProcessCalled = TRUE; + m_pHttpServer.RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error"); + } + } + } + } + } + CATCH_RETURN(); + + return S_OK; +} + +// +// Shutsdown all applications in the application hashtable +// Only called by OnGlobalStopListening. +// +VOID +APPLICATION_MANAGER::ShutDown() +{ + // We are guaranteed to only have one outstanding OnGlobalStopListening event at a time + // However, it is possible to receive multiple OnGlobalStopListening events + // Protect against this by checking if we already shut down. + g_fInShutdown = TRUE; + + // During shutdown we lock until we delete the application + SRWExclusiveLock lock(m_srwLock); + for (auto &pair : m_pApplicationInfoHash) + { + pair.second->ShutDownApplication(/* fServerInitiated */ true); + pair.second = nullptr; + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h new file mode 100644 index 0000000000..a9fdde6a5f --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.h @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "applicationinfo.h" +#include "multisz.h" +#include "exceptions.h" +#include + +// +// This class will manage the lifecycle of all Asp.Net Core applciation +// It should be global singleton. +// Should always call GetInstance to get the object instance +// + + +class APPLICATION_MANAGER +{ +public: + + HRESULT + GetOrCreateApplicationInfo( + _In_ IHttpContext& pHttpContext, + _Out_ std::shared_ptr& ppApplicationInfo + ); + + HRESULT + RecycleApplicationFromManager( + _In_ LPCWSTR pszApplicationId + ); + + VOID + ShutDown(); + + APPLICATION_MANAGER(HMODULE hModule, IHttpServer& pHttpServer) : + m_pApplicationInfoHash(NULL), + m_fDebugInitialize(FALSE), + m_pHttpServer(pHttpServer), + m_handlerResolver(hModule, pHttpServer) + { + InitializeSRWLock(&m_srwLock); + } + +private: + + std::unordered_map> m_pApplicationInfoHash; + SRWLOCK m_srwLock {}; + BOOL m_fDebugInitialize; + IHttpServer &m_pHttpServer; + HandlerResolver m_handlerResolver; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema.xml b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema.xml deleted file mode 100644 index d65be07195..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema_v2.xml similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml rename to src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_schema_v2.xml diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc index 9d0dfa7f83..9a110e2920 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/aspnetcoremodule.rc @@ -10,7 +10,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash +#define FileDescription "IIS ASP.NET Core Module V2. Commit: " CommitHash ///////////////////////////////////////////////////////////////////////////// // @@ -76,7 +76,7 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "CompanyName", "Microsoft Corporation" + VALUE "CompanyName", "Microsoft" VALUE "FileDescription", FileDescription VALUE "FileVersion", FileVersionStr VALUE "InternalName", "aspnetcore" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp new file mode 100644 index 0000000000..3fce85afdd --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp @@ -0,0 +1,165 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "applicationinfo.h" +#include "applicationmanager.h" +#include "proxymodule.h" +#include "globalmodule.h" +#include "acache.h" +#include "debugutil.h" +#include "resources.h" +#include "exceptions.h" +#include "EventLog.h" + +DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll"); + +HANDLE g_hEventLog = nullptr; +BOOL g_fRecycleProcessCalled = FALSE; +BOOL g_fInShutdown = FALSE; +HINSTANCE g_hServerModule; + +VOID +StaticCleanup() +{ + if (g_hEventLog != nullptr) + { + DeregisterEventSource(g_hEventLog); + g_hEventLog = nullptr; + } + + DebugStop(); + ALLOC_CACHE_HANDLER::StaticTerminate(); +} + +BOOL WINAPI DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + UNREFERENCED_PARAMETER(lpReserved); + + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + + ALLOC_CACHE_HANDLER::StaticInitialize(); + g_hServerModule = hModule; + DisableThreadLibraryCalls(hModule); + DebugInitialize(hModule); + break; + case DLL_PROCESS_DETACH: + + // IIS can cause dll detach to occur before we receive global notifications + // For example, when we switch the bitness of the worker process, + // this is a bug in IIS. To try to avoid AVs, we will set a global flag + g_fInShutdown = TRUE; + StaticCleanup(); + default: + break; + } + + return TRUE; +} + +HRESULT +__stdcall +RegisterModule( +DWORD dwServerVersion, +IHttpModuleRegistrationInfo * pModuleInfo, +IHttpServer * pHttpServer +) try +/*++ + +Routine description: + +Function called by IIS immediately after loading the module, used to let +IIS know what notifications the module is interested in + +Arguments: + +dwServerVersion - IIS version the module is being loaded on +pModuleInfo - info regarding this module +pHttpServer - callback functions which can be used by the module at +any point + +Return value: + +HRESULT + +--*/ +{ + HKEY hKey {}; + BOOL fDisableANCM = FALSE; + + UNREFERENCED_PARAMETER(dwServerVersion); + + if (pHttpServer->IsCommandLineLaunch()) + { + g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); + } + else + { + g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER); + } + + // check whether the feature is disabled due to security reason + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType = 0; + DWORD dwData = 0; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DisableANCM", + nullptr, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + fDisableANCM = (dwData != 0); + } + + RegCloseKey(hKey); + } + + if (fDisableANCM) + { + // Logging + EventLog::Warn( + ASPNETCORE_EVENT_MODULE_DISABLED, + ASPNETCORE_EVENT_MODULE_DISABLED_MSG); + // this will return 500 error to client + // as we did not register the module + return S_OK; + } + + // + // Create the factory before any static initialization. + // The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any + // static object initialized. + // + + auto applicationManager = std::make_shared(g_hServerModule, *pHttpServer); + auto moduleFactory = std::make_unique(pModuleInfo->GetId(), applicationManager); + + RETURN_IF_FAILED(pModuleInfo->SetRequestNotifications( + moduleFactory.release(), + RQ_EXECUTE_REQUEST_HANDLER, + 0)); +; + auto pGlobalModule = std::make_unique(std::move(applicationManager)); + + RETURN_IF_FAILED(pModuleInfo->SetGlobalNotifications( + pGlobalModule.release(), + GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop + GL_STOP_LISTENING)); // worker process stop or recycle + + return S_OK; +} +CATCH_RETURN() diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/globalmodule.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp similarity index 64% rename from src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/globalmodule.cpp rename to src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp index f4c6257bb4..abe08964cd 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/globalmodule.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp @@ -1,9 +1,13 @@ -#include "precomp.hxx" +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. -ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE( - APPLICATION_MANAGER* pApplicationManager) +#include "globalmodule.h" + +extern BOOL g_fInShutdown; + +ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE(std::shared_ptr pApplicationManager) noexcept + :m_pApplicationManager(std::move(pApplicationManager)) { - m_pApplicationManager = pApplicationManager; } // @@ -17,17 +21,16 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening( { UNREFERENCED_PARAMETER(pProvider); + LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening"); + if (g_fInShutdown) { // Avoid receiving two shutudown notifications. return GL_NOTIFICATION_CONTINUE; } - DBG_ASSERT(m_pApplicationManager); - // we should let application manager to shutdown all allication - // and dereference it as some requests may still reference to application manager m_pApplicationManager->ShutDown(); - m_pApplicationManager = NULL; + m_pApplicationManager = nullptr; // Return processing to the pipeline. return GL_NOTIFICATION_CONTINUE; @@ -49,14 +52,16 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange( // Retrieve the path that has changed. PCWSTR pwszChangePath = pProvider->GetChangePath(); + LOG_INFOF(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange '%ls'", pwszChangePath); + // Test for an error. - if (NULL != pwszChangePath && + if (nullptr != pwszChangePath && _wcsicmp(pwszChangePath, L"MACHINE") != 0 && _wcsicmp(pwszChangePath, L"MACHINE/WEBROOT") != 0) { - if (m_pApplicationManager != NULL) + if (m_pApplicationManager) { - m_pApplicationManager->RecycleApplicationFromManager(pwszChangePath); + m_pApplicationManager->RecycleApplicationFromManager(pwszChangePath); } } diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/globalmodule.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h similarity index 56% rename from src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/globalmodule.h rename to src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h index aca1857051..80f047e08d 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/Inc/globalmodule.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h @@ -3,21 +3,22 @@ #pragma once -class ASPNET_CORE_GLOBAL_MODULE : public CGlobalModule +#include "applicationmanager.h" + +class ASPNET_CORE_GLOBAL_MODULE : NonCopyable, public CGlobalModule { public: ASPNET_CORE_GLOBAL_MODULE( - APPLICATION_MANAGER* pApplicationManager - ); + std::shared_ptr pApplicationManager + ) noexcept; - ~ASPNET_CORE_GLOBAL_MODULE() - { - } + virtual ~ASPNET_CORE_GLOBAL_MODULE() = default; - VOID Terminate() + VOID Terminate() override { + LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::Terminate"); // Remove the class from memory. delete this; } @@ -25,13 +26,13 @@ public: GLOBAL_NOTIFICATION_STATUS OnGlobalStopListening( _In_ IGlobalStopListeningProvider * pProvider - ); + ) override; GLOBAL_NOTIFICATION_STATUS OnGlobalConfigurationChange( _In_ IGlobalConfigurationChangeProvider * pProvider - ); + ) override; private: - APPLICATION_MANAGER * m_pApplicationManager; + std::shared_ptr m_pApplicationManager; }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp new file mode 100644 index 0000000000..01707ffa9e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp @@ -0,0 +1,213 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "proxymodule.h" + +#include "applicationmanager.h" +#include "applicationinfo.h" +#include "exceptions.h" +#include "DisconnectHandler.h" +#include "SRWExclusiveLock.h" + +extern BOOL g_fInShutdown; + +__override + +ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept + : m_pApplicationManager(std::move(applicationManager)), + m_moduleId(moduleId) +{ +} + +HRESULT +ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator +) +{ + + #pragma warning( push ) + #pragma warning ( disable : 26409 ) // Disable "Avoid using new" + *ppModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(m_moduleId, m_pApplicationManager); + #pragma warning( push ) + if (*ppModule == nullptr) + { + return E_OUTOFMEMORY; + } + return S_OK; +} + +__override +VOID +ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate( + VOID +) noexcept +/*++ + +Routine description: + + Function called by IIS for global (non-request-specific) notifications + +Arguments: + + None. + +Return value: + + None + +--*/ +{ + delete this; +} + +ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept + : m_pApplicationManager(std::move(applicationManager)), + m_pApplicationInfo(nullptr), + m_pHandler(nullptr), + m_moduleId(moduleId), + m_pDisconnectHandler(nullptr) +{ + InitializeSRWLock(&m_requestLock); +} + +ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE() +{ + RemoveDisconnectHandler(); +} + +__override +REQUEST_NOTIFICATION_STATUS +ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * +) +{ + HRESULT hr = S_OK; + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; + + TraceContextScope traceScope(pHttpContext->GetTraceContext()); + // We don't want OnAsyncCompletion to complete request before OnExecuteRequestHandler exits + auto lock = SRWExclusiveLock(m_requestLock); + + try + { + if (g_fInShutdown) + { + FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); + } + + FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo( + *pHttpContext, + m_pApplicationInfo)); + + FINISHED_IF_FAILED(m_pApplicationInfo->CreateHandler(*pHttpContext, m_pHandler)); + + SetupDisconnectHandler(pHttpContext); + + retVal = m_pHandler->OnExecuteRequestHandler(); + } + catch (...) + { + hr = OBSERVE_CAUGHT_EXCEPTION(); + } + +Finished: + if (FAILED(LOG_IF_FAILED(hr))) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + if (hr == HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)) + { + pHttpContext->GetResponse()->SetStatus(503, "Service Unavailable", 0, hr); + } + else + { + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); + } + } + + return HandleNotificationStatus(retVal); +} + +__override +REQUEST_NOTIFICATION_STATUS +ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion( + IHttpContext * pHttpContext, + DWORD, + BOOL, + IHttpEventProvider *, + IHttpCompletionInfo * pCompletionInfo +) +{ + TraceContextScope traceScope(pHttpContext->GetTraceContext()); + // We don't want OnAsyncCompletion to complete request before OnExecuteRequestHandler exits + auto lock = SRWExclusiveLock(m_requestLock); + + try + { + return HandleNotificationStatus(m_pHandler->OnAsyncCompletion( + pCompletionInfo->GetCompletionBytes(), + pCompletionInfo->GetCompletionStatus())); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + return HandleNotificationStatus(RQ_NOTIFICATION_FINISH_REQUEST); + } +} + +REQUEST_NOTIFICATION_STATUS ASPNET_CORE_PROXY_MODULE::HandleNotificationStatus(REQUEST_NOTIFICATION_STATUS status) noexcept +{ + if (status != RQ_NOTIFICATION_PENDING) + { + RemoveDisconnectHandler(); + } + + return status; +} + +void ASPNET_CORE_PROXY_MODULE::SetupDisconnectHandler(IHttpContext * pHttpContext) +{ + auto connection = pHttpContext + ->GetConnection(); + + // connection might be null in when applicationInitialization is running + if (connection == nullptr) + { + return; + } + + auto moduleContainer = connection->GetModuleContextContainer(); + + #pragma warning( push ) + #pragma warning ( disable : 26466 ) // Disable "Don't use static_cast downcasts". We build without RTTI support so dynamic_cast is not available + auto pDisconnectHandler = static_cast(moduleContainer->GetConnectionModuleContext(m_moduleId)); + #pragma warning( push ) + + if (pDisconnectHandler == nullptr) + { + auto newHandler = std::make_unique(pHttpContext->GetConnection()); + pDisconnectHandler = newHandler.get(); + // ModuleContextContainer takes ownership of disconnectHandler + // we are trusting that it would not release it before deleting the context + LOG_IF_FAILED(moduleContainer->SetConnectionModuleContext(static_cast(newHandler.release()), m_moduleId)); + } + + // make code analysis happy + if (pDisconnectHandler != nullptr) + { + pDisconnectHandler->SetHandler(::ReferenceRequestHandler(m_pHandler.get())); + m_pDisconnectHandler = pDisconnectHandler; + } +} + +void ASPNET_CORE_PROXY_MODULE::RemoveDisconnectHandler() noexcept +{ + auto handler = m_pDisconnectHandler; + m_pDisconnectHandler = nullptr; + + if (handler != nullptr) + { + handler->RemoveHandler(); + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h new file mode 100644 index 0000000000..8f5b680e36 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "applicationinfo.h" +#include "irequesthandler.h" +#include "applicationmanager.h" +#include "DisconnectHandler.h" + +extern HTTP_MODULE_ID g_pModuleId; + +class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule +{ + public: + + ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept; + + ~ASPNET_CORE_PROXY_MODULE(); + + void * operator new(size_t size, IModuleAllocator * pPlacement) + { + return pPlacement->AllocateMemory(static_cast(size)); + } + + VOID + operator delete( + void * + ) + {} + + __override + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * pProvider + ) override; + + __override + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + IHttpContext * pHttpContext, + DWORD dwNotification, + BOOL fPostNotification, + IHttpEventProvider * pProvider, + IHttpCompletionInfo * pCompletionInfo + ) override; + + + private: + REQUEST_NOTIFICATION_STATUS + HandleNotificationStatus(REQUEST_NOTIFICATION_STATUS status) noexcept; + + void SetupDisconnectHandler(IHttpContext * pHttpContext); + void RemoveDisconnectHandler() noexcept; + + std::shared_ptr m_pApplicationManager; + std::shared_ptr m_pApplicationInfo; + std::unique_ptr m_pHandler; + HTTP_MODULE_ID m_moduleId; + DisconnectHandler * m_pDisconnectHandler; + SRWLOCK m_requestLock {}; +}; + +class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory +{ + public: + ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept; + virtual ~ASPNET_CORE_PROXY_MODULE_FACTORY() = default; + + HRESULT + GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator + ) override; + + VOID + Terminate() noexcept override; + + private: + std::shared_ptr m_pApplicationManager; + HTTP_MODULE_ID m_moduleId; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/resource.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/resource.h new file mode 100644 index 0000000000..289df99bc3 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by HtmlResponses.rc +// +#define IN_PROCESS_SHIM_STATIC_HTML 101 +#define OUT_OF_PROCESS_SHIM_STATIC_HTML 102 +#define UNKNOWN_HOSTING_STATIC_HTML 103 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp deleted file mode 100644 index 63efb9e889..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "precomp.hxx" - -APPLICATION_INFO::~APPLICATION_INFO() -{ - if (m_pAppOfflineHtm != NULL) - { - m_pAppOfflineHtm->DereferenceAppOfflineHtm(); - m_pAppOfflineHtm = NULL; - } - - if (m_pFileWatcherEntry != NULL) - { - // Mark the entry as invalid, - // StopMonitor will close the file handle and trigger a FCN - // the entry will delete itself when processing this FCN - m_pFileWatcherEntry->MarkEntryInValid(); - m_pFileWatcherEntry->StopMonitor(); - m_pFileWatcherEntry->DereferenceFileWatcherEntry(); - m_pFileWatcherEntry = NULL; - } - - if (m_pApplication != NULL) - { - // shutdown the application - m_pApplication->ShutDown(); - m_pApplication->DereferenceApplication(); - m_pApplication = NULL; - } - - // configuration should be dereferenced after application shutdown - // since the former will use it during shutdown - if (m_pConfiguration != NULL) - { - // Need to dereference the configuration instance - m_pConfiguration->DereferenceConfiguration(); - m_pConfiguration = NULL; - } -} - -HRESULT -APPLICATION_INFO::Initialize( - _In_ ASPNETCORE_CONFIG *pConfiguration, - _In_ FILE_WATCHER *pFileWatcher -) -{ - HRESULT hr = S_OK; - - DBG_ASSERT(pConfiguration); - DBG_ASSERT(pFileWatcher); - - m_pConfiguration = pConfiguration; - - // reference the configuration instance to prevent it will be not release - // earlier in case of configuration change and shutdown - m_pConfiguration->ReferenceConfiguration(); - - hr = m_applicationInfoKey.Initialize(pConfiguration->QueryConfigPath()->QueryStr()); - if (FAILED(hr)) - { - goto Finished; - } - - if (m_pFileWatcherEntry == NULL) - { - m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pFileWatcher); - if (m_pFileWatcherEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - } - - UpdateAppOfflineFileHandle(); - -Finished: - return hr; -} - -HRESULT -APPLICATION_INFO::StartMonitoringAppOffline() -{ - HRESULT hr = S_OK; - if (m_pFileWatcherEntry != NULL) - { - hr = m_pFileWatcherEntry->Create(m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), L"app_offline.htm", this, NULL); - } - return hr; -} - -// -// Called by the file watcher when the app_offline.htm's file status has been changed. -// If it finds it, we will call recycle on the application. -// -VOID -APPLICATION_INFO::UpdateAppOfflineFileHandle() -{ - STRU strFilePath; - UTILITY::ConvertPathToFullPath(L".\\app_offline.htm", - m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr(), - &strFilePath); - APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; - APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; - - ReferenceApplicationInfo(); - - if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) && - GetLastError() == ERROR_FILE_NOT_FOUND) - { - // Check if app offline was originally present. - // if it was, log that app_offline has been dropped. - if (m_fAppOfflineFound) - { - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED, - strEventMsg.QueryStr()); - } - } - - m_fAppOfflineFound = FALSE; - } - else - { - pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); - - if (pNewAppOfflineHtm != NULL) - { - if (pNewAppOfflineHtm->Load()) - { - // - // loaded the new app_offline.htm - // - pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); - - if (pOldAppOfflineHtm != NULL) - { - pOldAppOfflineHtm->DereferenceAppOfflineHtm(); - pOldAppOfflineHtm = NULL; - } - } - else - { - // ignored the new app_offline file because the file does not exist. - pNewAppOfflineHtm->DereferenceAppOfflineHtm(); - pNewAppOfflineHtm = NULL; - } - } - - m_fAppOfflineFound = TRUE; - - // recycle the application - if (m_pApplication != NULL) - { - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, - m_pApplication->QueryConfig()->QueryApplicationPath()->QueryStr()))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, - strEventMsg.QueryStr()); - } - - RecycleApplication(); - } - } - - DereferenceApplicationInfo(); -} - -HRESULT -APPLICATION_INFO::EnsureApplicationCreated() -{ - HRESULT hr = S_OK; - BOOL fLocked = FALSE; - APPLICATION* pApplication = NULL; - STACK_STRU(struFileName, 300); // >MAX_PATH - STRU struHostFxrDllLocation; - - if (m_pApplication != NULL) - { - goto Finished; - } - - if (m_pApplication == NULL) - { - AcquireSRWLockExclusive(&m_srwLock); - fLocked = TRUE; - if (m_pApplication != NULL) - { - goto Finished; - } - - // - // in case of app offline, we don't want to create a new application now - // - if (!m_fAppOfflineFound) - { - - // Move the request handler check inside of the lock - // such that only one request finds and loads it. - // FindRequestHandlerAssembly obtains a global lock, but after releasing the lock, - // there is a period where we could call - - hr = FindRequestHandlerAssembly(); - if (FAILED(hr)) - { - goto Finished; - } - - if (m_pfnAspNetCoreCreateApplication == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); - goto Finished; - } - - hr = m_pfnAspNetCoreCreateApplication(m_pServer, m_pConfiguration, &pApplication); - if (FAILED(hr)) - { - goto Finished; - } - m_pApplication = pApplication; - } - } - -Finished: - if (fLocked) - { - ReleaseSRWLockExclusive(&m_srwLock); - } - return hr; -} - -HRESULT -APPLICATION_INFO::FindRequestHandlerAssembly() -{ - HRESULT hr = S_OK; - BOOL fLocked = FALSE; - STACK_STRU(struFileName, 256); - - if (g_fAspnetcoreRHLoadedError) - { - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - goto Finished; - } - else if (!g_fAspnetcoreRHAssemblyLoaded) - { - AcquireSRWLockExclusive(&g_srwLock); - fLocked = TRUE; - if (g_fAspnetcoreRHLoadedError) - { - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - goto Finished; - } - if (g_fAspnetcoreRHAssemblyLoaded) - { - goto Finished; - } - - if (m_pConfiguration->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) - { - if (FAILED(hr = FindNativeAssemblyFromHostfxr(&struFileName))) - { - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_INPROCESS_RH_MISSING, - strEventMsg.QueryStr()); - } - - goto Finished; - } - } - else - { - if (FAILED(hr = FindNativeAssemblyFromGlobalLocation(&struFileName))) - { - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, - strEventMsg.QueryStr()); - } - - goto Finished; - } - } - - g_hAspnetCoreRH = LoadLibraryW(struFileName.QueryStr()); - if (g_hAspnetCoreRH == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - g_pfnAspNetCoreCreateApplication = (PFN_ASPNETCORE_CREATE_APPLICATION) - GetProcAddress(g_hAspnetCoreRH, "CreateApplication"); - if (g_pfnAspNetCoreCreateApplication == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - g_pfnAspNetCoreCreateRequestHandler = (PFN_ASPNETCORE_CREATE_REQUEST_HANDLER) - GetProcAddress(g_hAspnetCoreRH, "CreateRequestHandler"); - if (g_pfnAspNetCoreCreateRequestHandler == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - g_fAspnetcoreRHAssemblyLoaded = TRUE; - } - -Finished: - // - // Question: we remember the load failure so that we will not try again. - // User needs to check whether the fuction pointer is NULL - // - m_pfnAspNetCoreCreateApplication = g_pfnAspNetCoreCreateApplication; - m_pfnAspNetCoreCreateRequestHandler = g_pfnAspNetCoreCreateRequestHandler; - if (!g_fAspnetcoreRHLoadedError && FAILED(hr)) - { - g_fAspnetcoreRHLoadedError = TRUE; - } - - if (fLocked) - { - ReleaseSRWLockExclusive(&g_srwLock); - } - return hr; -} - -HRESULT -APPLICATION_INFO::FindNativeAssemblyFromGlobalLocation(STRU* struFilename) -{ - HRESULT hr = S_OK; - DWORD dwSize = MAX_PATH; - BOOL fDone = FALSE; - DWORD dwPosition = 0; - - // Though we could call LoadLibrary(L"aspnetcorerh.dll") relying the OS to solve - // the path (the targeted dll is the same folder of w3wp.exe/iisexpress) - // let's still load with full path to avoid security issue - if (FAILED(hr = struFilename->Resize(dwSize + 20))) - { - goto Finished; - } - - while (!fDone) - { - DWORD dwReturnedSize = GetModuleFileNameW(g_hModule, struFilename->QueryStr(), dwSize); - if (dwReturnedSize == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - fDone = TRUE; - goto Finished; - } - else if ((dwReturnedSize == dwSize) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) - { - dwSize *= 2; // smaller buffer. increase the buffer and retry - if (FAILED(hr = struFilename->Resize(dwSize + 20))) // + 20 for aspnetcorerh.dll - { - goto Finished; - } - } - else - { - fDone = TRUE; - } - } - - if (FAILED(hr = struFilename->SyncWithBuffer())) - { - goto Finished; - } - dwPosition = struFilename->LastIndexOf(L'\\', 0); - struFilename->QueryStr()[dwPosition] = L'\0'; - - if (FAILED(hr = struFilename->SyncWithBuffer()) || - FAILED(hr = struFilename->Append(L"\\")) || - FAILED(hr = struFilename->Append(g_pwzAspnetcoreRequestHandlerName))) - { - goto Finished; - } - -Finished: - return hr; -} - -// -// Tries to find aspnetcorerh.dll from the application -// Calls into hostfxr.dll to find it. -// Will leave hostfxr.dll loaded as it will be used again to call hostfxr_main. -// -HRESULT -APPLICATION_INFO::FindNativeAssemblyFromHostfxr( - STRU* struFilename -) -{ - HRESULT hr = S_OK; - STRU struApplicationFullPath; - STRU struNativeSearchPaths; - STRU struNativeDllLocation; - HMODULE hmHostFxrDll = NULL; - INT intHostFxrExitCode = 0; - INT intIndex = -1; - INT intPrevIndex = 0; - BOOL fFound = FALSE; - DWORD dwBufferSize = 1024 * 10; - DWORD dwRequiredBufferSize = 0; - - DBG_ASSERT(struFileName != NULL); - - hmHostFxrDll = LoadLibraryW(m_pConfiguration->QueryHostFxrFullPath()); - - if (hmHostFxrDll == NULL) - { - // Could not load hostfxr - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = (hostfxr_get_native_search_directories_fn) - GetProcAddress(hmHostFxrDll, "hostfxr_get_native_search_directories"); - - if (pFnHostFxrSearchDirectories == NULL) - { - // Host fxr version is incorrect (need a higher version). - // TODO log error - hr = E_FAIL; - goto Finished; - } - - if (FAILED(hr = struNativeSearchPaths.Resize(dwBufferSize))) - { - goto Finished; - } - - while (TRUE) - { - intHostFxrExitCode = pFnHostFxrSearchDirectories( - m_pConfiguration->QueryHostFxrArgCount(), - m_pConfiguration->QueryHostFxrArguments(), - struNativeSearchPaths.QueryStr(), - dwBufferSize, - &dwRequiredBufferSize - ); - - if (intHostFxrExitCode == 0) - { - break; - } - else if (dwRequiredBufferSize > dwBufferSize) - { - dwBufferSize = dwRequiredBufferSize + 1; // for null terminator - - if (FAILED(hr = struNativeSearchPaths.Resize(dwBufferSize))) - { - goto Finished; - } - } - else - { - hr = E_FAIL; - // Log "Error finding native search directories from aspnetcore application. - goto Finished; - } - } - - if (FAILED(hr = struNativeSearchPaths.SyncWithBuffer())) - { - goto Finished; - } - - fFound = FALSE; - - // The native search directories are semicolon delimited. - // Split on semicolons, append aspnetcorerh.dll, and check if the file exists. - while ((intIndex = struNativeSearchPaths.IndexOf(L";", intPrevIndex)) != -1) - { - if (FAILED(hr = struNativeDllLocation.Copy(&struNativeSearchPaths.QueryStr()[intPrevIndex], intIndex - intPrevIndex))) - { - goto Finished; - } - - if (!struNativeDllLocation.EndsWith(L"\\")) - { - if (FAILED(hr = struNativeDllLocation.Append(L"\\"))) - { - goto Finished; - } - } - - if (FAILED(hr = struNativeDllLocation.Append(g_pwzAspnetcoreRequestHandlerName))) - { - goto Finished; - } - - if (UTILITY::CheckIfFileExists(struNativeDllLocation.QueryStr())) - { - if (FAILED(hr = struFilename->Copy(struNativeDllLocation))) - { - goto Finished; - } - fFound = TRUE; - break; - } - - intPrevIndex = intIndex + 1; - } - - if (!fFound) - { - hr = E_FAIL; - goto Finished; - } - -Finished: - if (FAILED(hr) && hmHostFxrDll != NULL) - { - FreeLibrary(hmHostFxrDll); - } - return hr; -} - -VOID -APPLICATION_INFO::RecycleApplication() -{ - APPLICATION* pApplication = NULL; - HANDLE hThread = INVALID_HANDLE_VALUE; - BOOL fLockAcquired = FALSE; - - if (m_pApplication != NULL) - { - AcquireSRWLockExclusive(&m_srwLock); - fLockAcquired = TRUE; - if (m_pApplication != NULL) - { - pApplication = m_pApplication; - if (pApplication->QueryConfig()->QueryHostingModel() == HOSTING_OUT_PROCESS) - { - // - // For inprocess, need to set m_pApplication to NULL first to - // avoid mapping new request to the recycled application. - // Outofprocess application instance will be created for new request - // For inprocess, as recycle will lead to shutdown later, leave m_pApplication - // to not block incoming requests till worker process shutdown - // - m_pApplication = NULL; - } - else - { - // - // For inprocess, need hold the application till shutdown is called - // Bump the reference counter as DoRecycleApplication will do dereference - // - pApplication->ReferenceApplication(); - } - - hThread = CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)DoRecycleApplication, - pApplication, // thread function arguments - 0, // default creation flags - NULL); // receive thread identifier - } - - if (hThread == NULL) - { - if (!g_fRecycleProcessCalled) - { - g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"On Demand by AspNetCore Module for recycle application failure"); - } - } - else - { - // Closing a thread handle does not terminate the associated thread or remove the thread object. - CloseHandle(hThread); - } - - if (fLockAcquired) - { - ReleaseSRWLockExclusive(&m_srwLock); - } - } -} - - -VOID -APPLICATION_INFO::DoRecycleApplication( - LPVOID lpParam) -{ - APPLICATION* pApplication = static_cast(lpParam); - - // No lock required - - if (pApplication != NULL) - { - // Recycle will call shutdown for out of process - pApplication->Recycle(); - - // Decrement the ref count as we reference it in RecycleApplication. - pApplication->DereferenceApplication(); - } -} - - -VOID -APPLICATION_INFO::ShutDownApplication() -{ - APPLICATION* pApplication = NULL; - BOOL fLockAcquired = FALSE; - - // pApplication can be NULL due to app_offline - if (m_pApplication != NULL) - { - AcquireSRWLockExclusive(&m_srwLock); - fLockAcquired = TRUE; - if (m_pApplication != NULL) - { - pApplication = m_pApplication; - - // Set m_pApplication to NULL first to prevent anyone from using it - m_pApplication = NULL; - pApplication->ShutDown(); - pApplication->DereferenceApplication(); - } - - if (fLockAcquired) - { - ReleaseSRWLockExclusive(&m_srwLock); - } - } -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx deleted file mode 100644 index fd83a19818..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/applicationmanager.cxx +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "precomp.hxx" - -// The application manager is a singleton across ANCM. -APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; - -// -// Retrieves the application info from the application manager -// Will create the application info if it isn't initalized -// -HRESULT -APPLICATION_MANAGER::GetOrCreateApplicationInfo( - _In_ IHttpServer* pServer, - _In_ ASPNETCORE_CONFIG* pConfig, - _Out_ APPLICATION_INFO ** ppApplicationInfo -) -{ - HRESULT hr = S_OK; - APPLICATION_INFO *pApplicationInfo = NULL; - APPLICATION_INFO_KEY key; - BOOL fExclusiveLock = FALSE; - BOOL fMixedHostingModelError = FALSE; - BOOL fDuplicatedInProcessApp = FALSE; - PCWSTR pszApplicationId = NULL; - STACK_STRU ( strEventMsg, 256 ); - - DBG_ASSERT(pServer); - DBG_ASSERT(pConfig); - DBG_ASSERT(ppApplicationInfo); - - *ppApplicationInfo = NULL; - - // The configuration path is unique for each application and is used for the - // key in the applicationInfoHash. - pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); - hr = key.Initialize(pszApplicationId); - if (FAILED(hr)) - { - goto Finished; - } - - // When accessing the m_pApplicationInfoHash, we need to acquire the application manager - // lock to avoid races on setting state. - AcquireSRWLockShared(&m_srwLock); - if (g_fInShutdown) - { - ReleaseSRWLockShared(&m_srwLock); - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; - } - m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); - ReleaseSRWLockShared(&m_srwLock); - - if (*ppApplicationInfo == NULL) - { - // Check which hosting model we want to support - switch (pConfig->QueryHostingModel()) - { - case HOSTING_IN_PROCESS: - if (m_pApplicationInfoHash->Count() > 0) - { - // Only one inprocess app is allowed per IIS worker process - fDuplicatedInProcessApp = TRUE; - hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); - goto Finished; - } - break; - - case HOSTING_OUT_PROCESS: - break; - - default: - hr = E_UNEXPECTED; - goto Finished; - } - pApplicationInfo = new APPLICATION_INFO(pServer); - if (pApplicationInfo == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - AcquireSRWLockExclusive(&m_srwLock); - fExclusiveLock = TRUE; - if (g_fInShutdown) - { - // Already in shuting down. No need to create the application - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; - } - m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); - - if (*ppApplicationInfo != NULL) - { - // someone else created the application - delete pApplicationInfo; - pApplicationInfo = NULL; - goto Finished; - } - - // hosting model check. We do not allow mixed scenario for now - // could be changed in the future - if (m_hostingModel != HOSTING_UNKNOWN) - { - if (m_hostingModel != pConfig->QueryHostingModel()) - { - // hosting model does not match, error out - fMixedHostingModelError = TRUE; - hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); - goto Finished; - } - } - - hr = pApplicationInfo->Initialize(pConfig, m_pFileWatcher); - if (FAILED(hr)) - { - goto Finished; - } - - hr = m_pApplicationInfoHash->InsertRecord( pApplicationInfo ); - if (FAILED(hr)) - { - goto Finished; - } - - // - // first application will decide which hosting model allowed by this process - // - if (m_hostingModel == HOSTING_UNKNOWN) - { - m_hostingModel = pConfig->QueryHostingModel(); - } - - *ppApplicationInfo = pApplicationInfo; - pApplicationInfo->StartMonitoringAppOffline(); - - // Release the locko after starting to monitor for app offline to avoid races with it being dropped. - ReleaseSRWLockExclusive(&m_srwLock); - - fExclusiveLock = FALSE; - pApplicationInfo = NULL; - } - -Finished: - - if (fExclusiveLock) - { - ReleaseSRWLockExclusive(&m_srwLock); - } - - if (pApplicationInfo != NULL) - { - pApplicationInfo->DereferenceApplicationInfo(); - pApplicationInfo = NULL; - } - - if (FAILED(hr)) - { - if (fDuplicatedInProcessApp) - { - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, - pszApplicationId))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, - strEventMsg.QueryStr()); - } - } - else if (fMixedHostingModelError) - { - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, - pszApplicationId, - pConfig->QueryHostingModel()))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, - strEventMsg.QueryStr()); - } - } - else - { - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, - pszApplicationId, - hr))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, - strEventMsg.QueryStr()); - } - } - } - - return hr; -} - - -// -// If the application's configuration was changed, -// append the configuration path to the config change context. -// -BOOL -APPLICATION_MANAGER::FindConfigChangedApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext) -{ - DBG_ASSERT(pEntry); - DBG_ASSERT(pvContext); - - // Config Change context contains the original config path that changed - // and a multiStr containing - CONFIG_CHANGE_CONTEXT* pContext = static_cast(pvContext); - STRU* pstruConfigPath = pEntry->QueryConfig()->QueryConfigPath(); - - // check if the application path contains our app/subapp by seeing if the config path - // starts with the notification path. - BOOL fChanged = pstruConfigPath->StartsWith(pContext->pstrPath, true); - if (fChanged) - { - DWORD dwLen = (DWORD)wcslen(pContext->pstrPath); - WCHAR wChar = pstruConfigPath->QueryStr()[dwLen]; - - // We need to check that the last character of the config path - // is either a null terminator or a slash. - // This checks the case where the config path was - // MACHINE/WEBROOT/site and your site path is MACHINE/WEBROOT/siteTest - if (wChar != L'\0' && wChar != L'/') - { - // not current app or sub app - fChanged = FALSE; - } - else - { - pContext->MultiSz.Append(*pstruConfigPath); - } - } - return fChanged; -} - -// -// Finds any applications affected by a configuration change and calls Recycle on them -// InProcess: Triggers g_httpServer->RecycleProcess() and keep the application inside of the manager. -// This will cause a shutdown event to occur through the global stop listening event. -// OutOfProcess: Removes all applications in the application manager and calls Recycle, which will call Shutdown, -// on each application. -// -HRESULT -APPLICATION_MANAGER::RecycleApplicationFromManager( - _In_ LPCWSTR pszApplicationId -) -{ - HRESULT hr = S_OK; - APPLICATION_INFO_KEY key; - DWORD dwPreviousCounter = 0; - APPLICATION_INFO_HASH* table = NULL; - CONFIG_CHANGE_CONTEXT context; - BOOL fKeepTable = FALSE; - - if (g_fInShutdown) - { - // We are already shutting down, ignore this event as a global configuration change event - // can occur after global stop listening for some reason. - return hr; - } - - AcquireSRWLockExclusive(&m_srwLock); - if (g_fInShutdown) - { - ReleaseSRWLockExclusive(&m_srwLock); - return hr; - } - - hr = key.Initialize(pszApplicationId); - if (FAILED(hr)) - { - goto Finished; - } - // Make a shallow copy of existing hashtable as we may need to remove nodes - // This will be used for finding differences in which applications are affected by a config change. - table = new APPLICATION_INFO_HASH(); - - if (table == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - // few application expected, small bucket size for hash table - if (FAILED(hr = table->Initialize(17 /*prime*/))) - { - goto Finished; - } - - context.pstrPath = pszApplicationId; - - // Keep track of the preview count of applications to know whether there are applications to delete - dwPreviousCounter = m_pApplicationInfoHash->Count(); - - // We don't want to hold the application manager lock for long time as it will block all incoming requests - // Don't call application shutdown inside the lock - m_pApplicationInfoHash->Apply(APPLICATION_INFO_HASH::ReferenceCopyToTable, static_cast(table)); - DBG_ASSERT(dwPreviousCounter == table->Count()); - - // Removed the applications which are impacted by the configurtion change - m_pApplicationInfoHash->DeleteIf(FindConfigChangedApplication, (PVOID)&context); - - if (dwPreviousCounter != m_pApplicationInfoHash->Count()) - { - if (m_hostingModel == HOSTING_IN_PROCESS) - { - // When we are inprocess, we need to keep the application the - // application manager that is being deleted. This is because we will always need to recycle the worker - // process and any requests that hit this worker process must be rejected (while out of process can - // start a new dotnet process). We will immediately call Recycle after this call. - DBG_ASSERT(m_pApplicationInfoHash->Count() == 0); - delete m_pApplicationInfoHash; - - // We don't want to delete the table as m_pApplicationInfoHash = table - fKeepTable = TRUE; - m_pApplicationInfoHash = table; - } - } - - if (m_pApplicationInfoHash->Count() == 0) - { - m_hostingModel = HOSTING_UNKNOWN; - } - - ReleaseSRWLockExclusive(&m_srwLock); - - // If we receive a request at this point. - // OutOfProcess: we will create a new application with new configuration - // InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess - // on the worker proocess - if (!context.MultiSz.IsEmpty()) - { - PCWSTR path = context.MultiSz.First(); - // Iterate through each of the paths that were shut down, - // calling RecycleApplication on each of them. - while (path != NULL) - { - APPLICATION_INFO* pRecord; - - // Application got recycled. Log an event - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG, - path))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_CONFIGURATION, - strEventMsg.QueryStr()); - } - - hr = key.Initialize(path); - if (FAILED(hr)) - { - goto Finished; - } - - table->FindKey(&key, &pRecord); - DBG_ASSERT(pRecord != NULL); - - // RecycleApplication is called on a separate thread. - pRecord->RecycleApplication(); - pRecord->DereferenceApplicationInfo(); - path = context.MultiSz.Next(path); - } - } - -Finished: - if (table != NULL && !fKeepTable) - { - table->Clear(); - delete table; - } - - if (FAILED(hr)) - { - // Failed to recycle an application. Log an event - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG, - pszApplicationId))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_RECYCLE_APP_FAILURE, - strEventMsg.QueryStr()); - } - // Need to recycle the process as we cannot recycle the application - if (!g_fRecycleProcessCalled) - { - g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error"); - } - } - - return hr; -} - -// -// Shutsdown all applications in the application hashtable -// Only called by OnGlobalStopListening. -// -VOID -APPLICATION_MANAGER::ShutDown() -{ - // We are guaranteed to only have one outstanding OnGlobalStopListening event at a time - // However, it is possible to receive multiple OnGlobalStopListening events - // Protect against this by checking if we already shut down. - g_fInShutdown = TRUE; - if (m_pApplicationInfoHash != NULL) - { - if (m_pFileWatcher != NULL) - { - delete m_pFileWatcher; - m_pFileWatcher = NULL; - } - - DBG_ASSERT(m_pApplicationInfoHash); - // During shutdown we lock until we delete the application - AcquireSRWLockExclusive(&m_srwLock); - - // Call shutdown on each application in the application manager - m_pApplicationInfoHash->Apply(ShutdownApplication, NULL); - m_pApplicationInfoHash->Clear(); - delete m_pApplicationInfoHash; - m_pApplicationInfoHash = NULL; - - ReleaseSRWLockExclusive(&m_srwLock); - } -} - -// -// Calls shutdown on each application. ApplicationManager's lock is held for duration of -// each shutdown call, guaranteeing another application cannot be created. -// -// static -VOID -APPLICATION_MANAGER::ShutdownApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext -) -{ - UNREFERENCED_PARAMETER(pvContext); - DBG_ASSERT(pEntry != NULL); - - pEntry->ShutDownApplication(); -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp deleted file mode 100644 index 2e4f87e542..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/dllmain.cpp +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "precomp.hxx" -#include - -HTTP_MODULE_ID g_pModuleId = NULL; -IHttpServer * g_pHttpServer = NULL; -HANDLE g_hEventLog = NULL; -BOOL g_fRecycleProcessCalled = FALSE; -PCWSTR g_pszModuleName = NULL; -HINSTANCE g_hModule; -HMODULE g_hAspnetCoreRH = NULL; -BOOL g_fAspnetcoreRHAssemblyLoaded = FALSE; -BOOL g_fAspnetcoreRHLoadedError = FALSE; -BOOL g_fInShutdown = FALSE; -DWORD g_dwAspNetCoreDebugFlags = 0; -DWORD g_dwActiveServerProcesses = 0; -SRWLOCK g_srwLock; -DWORD g_dwDebugFlags = 0; -PCSTR g_szDebugLabel = "ASPNET_CORE_MODULE"; -PCWSTR g_pwzAspnetcoreRequestHandlerName = L"aspnetcorerh.dll"; -PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; -PFN_ASPNETCORE_CREATE_REQUEST_HANDLER g_pfnAspNetCoreCreateRequestHandler; - -VOID -StaticCleanup() -{ - APPLICATION_MANAGER::Cleanup(); - if (g_hEventLog != NULL) - { - DeregisterEventSource(g_hEventLog); - g_hEventLog = NULL; - } -} - -BOOL WINAPI DllMain(HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) -{ - UNREFERENCED_PARAMETER(lpReserved); - - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - g_hModule = hModule; - DisableThreadLibraryCalls(hModule); - break; - case DLL_PROCESS_DETACH: - // IIS can cause dll detach to occur before we receive global notifications - // For example, when we switch the bitness of the worker process, - // this is a bug in IIS. To try to avoid AVs, we will set a global flag - g_fInShutdown = TRUE; - StaticCleanup(); - default: - break; - } - - return TRUE; -} - -HRESULT -__stdcall -RegisterModule( -DWORD dwServerVersion, -IHttpModuleRegistrationInfo * pModuleInfo, -IHttpServer * pHttpServer -) -/*++ - -Routine description: - -Function called by IIS immediately after loading the module, used to let -IIS know what notifications the module is interested in - -Arguments: - -dwServerVersion - IIS version the module is being loaded on -pModuleInfo - info regarding this module -pHttpServer - callback functions which can be used by the module at -any point - -Return value: - -HRESULT - ---*/ -{ - HRESULT hr = S_OK; - HKEY hKey; - BOOL fDisableANCM = FALSE; - ASPNET_CORE_PROXY_MODULE_FACTORY * pFactory = NULL; - ASPNET_CORE_GLOBAL_MODULE * pGlobalModule = NULL; - APPLICATION_MANAGER * pApplicationManager = NULL; - - UNREFERENCED_PARAMETER(dwServerVersion); - -#ifdef DEBUG - CREATE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); - g_dwDebugFlags = DEBUG_FLAGS_ANY; -#endif // DEBUG - - CREATE_DEBUG_PRINT_OBJECT; - - //LoadGlobalConfiguration(); - - InitializeSRWLock(&g_srwLock); - - g_pModuleId = pModuleInfo->GetId(); - g_pszModuleName = pModuleInfo->GetName(); - g_pHttpServer = pHttpServer; - - if (g_pHttpServer->IsCommandLineLaunch()) - { - g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); - } - else - { - g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_EVENT_PROVIDER); - } - - // check whether the feature is disabled due to security reason - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", - 0, - KEY_READ, - &hKey) == NO_ERROR) - { - DWORD dwType; - DWORD dwData; - DWORD cbData; - - cbData = sizeof(dwData); - if ((RegQueryValueEx(hKey, - L"DisableANCM", - NULL, - &dwType, - (LPBYTE)&dwData, - &cbData) == NO_ERROR) && - (dwType == REG_DWORD)) - { - fDisableANCM = (dwData != 0); - } - - cbData = sizeof(dwData); - if ((RegQueryValueEx(hKey, - L"DebugFlags", - NULL, - &dwType, - (LPBYTE)&dwData, - &cbData) == NO_ERROR) && - (dwType == REG_DWORD)) - { - g_dwAspNetCoreDebugFlags = dwData; - } - - RegCloseKey(hKey); - } - - if (fDisableANCM) - { - // Logging - STACK_STRU(strEventMsg, 256); - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_MODULE_DISABLED_MSG))) - { - UTILITY::LogEvent(g_hEventLog, - EVENTLOG_WARNING_TYPE, - ASPNETCORE_EVENT_MODULE_DISABLED, - strEventMsg.QueryStr()); - } - // this will return 500 error to client - // as we did not register the module - goto Finished; - } - - // - // Create the factory before any static initialization. - // The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any - // static object initialized. - // - pFactory = new ASPNET_CORE_PROXY_MODULE_FACTORY; - - if (pFactory == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pModuleInfo->SetRequestNotifications( - pFactory, - RQ_EXECUTE_REQUEST_HANDLER, - 0); - if (FAILED(hr)) - { - goto Finished; - } - - pFactory = NULL; - pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if(pApplicationManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pApplicationManager->Initialize(); - if(FAILED(hr)) - { - goto Finished; - } - pGlobalModule = NULL; - - pGlobalModule = new ASPNET_CORE_GLOBAL_MODULE(pApplicationManager); - if (pGlobalModule == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pModuleInfo->SetGlobalNotifications( - pGlobalModule, - GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop - GL_STOP_LISTENING); // worker process stop or recycle - - if (FAILED(hr)) - { - goto Finished; - } - pGlobalModule = NULL; - - hr = ALLOC_CACHE_HANDLER::StaticInitialize(); - if (FAILED(hr)) - { - goto Finished; - } - -Finished: - if (pGlobalModule != NULL) - { - delete pGlobalModule; - pGlobalModule = NULL; - } - - if (pFactory != NULL) - { - pFactory->Terminate(); - pFactory = NULL; - } - - return hr; -} - diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx deleted file mode 100644 index 4d3174937f..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/filewatcher.cxx +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "precomp.hxx" - -FILE_WATCHER::FILE_WATCHER() : - m_hCompletionPort(NULL), - m_hChangeNotificationThread(NULL), - m_fThreadExit(FALSE) -{ -} - -FILE_WATCHER::~FILE_WATCHER() -{ - if (m_hChangeNotificationThread != NULL) - { - DWORD dwRetryCounter = 20; // totally wait for 1s - DWORD dwExitCode = STILL_ACTIVE; - - // signal the file watch thread to exit - PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); - while (!m_fThreadExit && dwRetryCounter > 0) - { - if (GetExitCodeThread(m_hChangeNotificationThread, &dwExitCode)) - { - if (dwExitCode == STILL_ACTIVE) - { - // the file watcher thread will set m_fThreadExit before exit - WaitForSingleObject(m_hChangeNotificationThread, 50); - } - } - else - { - // fail to get thread status - // call terminitethread - TerminateThread(m_hChangeNotificationThread, 1); - m_fThreadExit = TRUE; - } - dwRetryCounter--; - } - - if (!m_fThreadExit) - { - TerminateThread(m_hChangeNotificationThread, 1); - } - - CloseHandle(m_hChangeNotificationThread); - m_hChangeNotificationThread = NULL; - } - - if (NULL != m_hCompletionPort) - { - CloseHandle(m_hCompletionPort); - m_hCompletionPort = NULL; - } -} - -HRESULT -FILE_WATCHER::Create( - VOID -) -{ - HRESULT hr = S_OK; - - m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, - NULL, - 0, - 0); - - if (m_hCompletionPort == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - m_hChangeNotificationThread = CreateThread(NULL, - 0, - ChangeNotificationThread, - this, - 0, - NULL); - - if (m_hChangeNotificationThread == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - - CloseHandle(m_hCompletionPort); - m_hCompletionPort = NULL; - - goto Finished; - } - -Finished: - return hr; -} - -DWORD -WINAPI -FILE_WATCHER::ChangeNotificationThread( - LPVOID pvArg -) -/*++ - -Routine Description: - -IO completion thread - -Arguments: - -None - -Return Value: - -Win32 error - ---*/ -{ - FILE_WATCHER * pFileMonitor; - BOOL fSuccess = FALSE; - DWORD cbCompletion = 0; - OVERLAPPED * pOverlapped = NULL; - DWORD dwErrorStatus; - ULONG_PTR completionKey; - - pFileMonitor = (FILE_WATCHER*)pvArg; - DBG_ASSERT(pFileMonitor != NULL); - - while (TRUE) - { - fSuccess = GetQueuedCompletionStatus( - pFileMonitor->m_hCompletionPort, - &cbCompletion, - &completionKey, - &pOverlapped, - INFINITE); - - DBG_ASSERT(fSuccess); - dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); - - if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) - { - break; - } - - DBG_ASSERT(pOverlapped != NULL); - if (pOverlapped != NULL) - { - FileWatcherCompletionRoutine( - dwErrorStatus, - cbCompletion, - pOverlapped); - } - pOverlapped = NULL; - cbCompletion = 0; - } - - pFileMonitor->m_fThreadExit = TRUE; - ExitThread(0); -} - -VOID -WINAPI -FILE_WATCHER::FileWatcherCompletionRoutine( - DWORD dwCompletionStatus, - DWORD cbCompletion, - OVERLAPPED * pOverlapped -) -/*++ - -Routine Description: - -Called when ReadDirectoryChangesW() completes - -Arguments: - -dwCompletionStatus - Error of completion -cbCompletion - Bytes of completion -pOverlapped - State of completion - -Return Value: - -None - ---*/ -{ - FILE_WATCHER_ENTRY * pMonitorEntry; - pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped); - - DBG_ASSERT(pMonitorEntry != NULL); - - pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); - - if (pMonitorEntry->QueryIsValid()) - { - // - // Continue monitoring - // - pMonitorEntry->Monitor(); - } - // - // Deference the counter not matter whether the monitor is valid - // Valid: Monitor increases the counter, need to reduce one - // InValid: Reduce the counter to free the entry - // - pMonitorEntry->DereferenceFileWatcherEntry(); - -} - - -FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : - _pFileMonitor(pFileMonitor), - _hDirectory(INVALID_HANDLE_VALUE), - _hImpersonationToken(NULL), - _pApplicationInfo(NULL), - _lStopMonitorCalled(0), - _cRefs(1), - _fIsValid(TRUE) -{ - _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; - InitializeSRWLock(&_srwLock); -} - -FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() -{ - _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; - - if (_hDirectory != INVALID_HANDLE_VALUE) - { - CloseHandle(_hDirectory); - _hDirectory = INVALID_HANDLE_VALUE; - } - - if (_hImpersonationToken != NULL) - { - CloseHandle(_hImpersonationToken); - _hImpersonationToken = NULL; - } -} - -#pragma warning(disable:4100) - -HRESULT -FILE_WATCHER_ENTRY::HandleChangeCompletion( - _In_ DWORD dwCompletionStatus, - _In_ DWORD cbCompletion -) -/*++ - -Routine Description: - -Handle change notification (see if any of associated config files -need to be flushed) - -Arguments: - -dwCompletionStatus - Completion status -cbCompletion - Bytes of completion - -Return Value: - -HRESULT - ---*/ -{ - HRESULT hr = S_OK; - FILE_NOTIFY_INFORMATION * pNotificationInfo; - BOOL fFileChanged = FALSE; - - AcquireSRWLockExclusive(&_srwLock); - if (!_fIsValid) - { - goto Finished; - } - - // When directory handle is closed then HandleChangeCompletion - // happens with cbCompletion = 0 and dwCompletionStatus = 0 - // From documentation it is not clear if that combination - // of return values is specific to closing handles or whether - // it could also mean an error condition. Hence we will maintain - // explicit flag that will help us determine if entry - // is being shutdown (StopMonitor() called) - // - if (_lStopMonitorCalled) - { - goto Finished; - } - - // - // There could be a FCN overflow - // Let assume the file got changed instead of checking files - // Othersie we have to cache the file info - // - if (cbCompletion == 0) - { - fFileChanged = TRUE; - } - else - { - pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); - DBG_ASSERT(pNotificationInfo != NULL); - - while (pNotificationInfo != NULL) - { - // - // check whether the monitored file got changed - // - if (_wcsnicmp(pNotificationInfo->FileName, - _strFileName.QueryStr(), - pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0) - { - fFileChanged = TRUE; - break; - } - // - // Advance to next notification - // - if (pNotificationInfo->NextEntryOffset == 0) - { - pNotificationInfo = NULL; - } - else - { - pNotificationInfo = (FILE_NOTIFY_INFORMATION*) - ((PBYTE)pNotificationInfo + - pNotificationInfo->NextEntryOffset); - } - } - } - - if (fFileChanged) - { - // - // so far we only monitoring app_offline - // - _pApplicationInfo->UpdateAppOfflineFileHandle(); - } - -Finished: - ReleaseSRWLockExclusive(&_srwLock); - return hr; -} - -#pragma warning( error : 4100 ) - -HRESULT -FILE_WATCHER_ENTRY::Monitor(VOID) -{ - HRESULT hr = S_OK; - DWORD cbRead; - - AcquireSRWLockExclusive(&_srwLock); - ReferenceFileWatcherEntry(); - ZeroMemory(&_overlapped, sizeof(_overlapped)); - - if (!ReadDirectoryChangesW(_hDirectory, - _buffDirectoryChanges.QueryPtr(), - _buffDirectoryChanges.QuerySize(), - FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline - FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, - &cbRead, - &_overlapped, - NULL)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - DereferenceFileWatcherEntry(); - } - - ReleaseSRWLockExclusive(&_srwLock); - return hr; -} - -VOID -FILE_WATCHER_ENTRY::StopMonitor(VOID) -{ - // - // Flag that monitoring is being stopped so that - // we know that HandleChangeCompletion() call - // can be ignored - // - InterlockedExchange(&_lStopMonitorCalled, 1); - - if (_hDirectory != INVALID_HANDLE_VALUE) - { - AcquireSRWLockExclusive(&_srwLock); - if (_hDirectory != INVALID_HANDLE_VALUE) - { - CloseHandle(_hDirectory); - _hDirectory = INVALID_HANDLE_VALUE; - DereferenceFileWatcherEntry(); - } - ReleaseSRWLockExclusive(&_srwLock); - } -} - -HRESULT -FILE_WATCHER_ENTRY::Create( - _In_ PCWSTR pszDirectoryToMonitor, - _In_ PCWSTR pszFileNameToMonitor, - _In_ APPLICATION_INFO* pApplicationInfo, - _In_ HANDLE hImpersonationToken -) -{ - HRESULT hr = S_OK; - BOOL fRet = FALSE; - - if (pszDirectoryToMonitor == NULL || - pszFileNameToMonitor == NULL || - pApplicationInfo == NULL) - { - DBG_ASSERT(FALSE); - hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); - goto Finished; - } - - // - //remember the application - // - _pApplicationInfo = pApplicationInfo; - - if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) - { - goto Finished; - } - - if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor))) - { - goto Finished; - } - - // - // Resize change buffer to something "reasonable" - // - if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)) - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto Finished; - } - - if (hImpersonationToken != NULL) - { - fRet = DuplicateHandle(GetCurrentProcess(), - hImpersonationToken, - GetCurrentProcess(), - &_hImpersonationToken, - 0, - FALSE, - DUPLICATE_SAME_ACCESS); - - if (!fRet) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - } - else - { - if (_hImpersonationToken != NULL) - { - CloseHandle(_hImpersonationToken); - _hImpersonationToken = NULL; - } - } - - _hDirectory = CreateFileW( - _strDirectoryName.QueryStr(), - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - NULL); - - if (_hDirectory == INVALID_HANDLE_VALUE) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (CreateIoCompletionPort( - _hDirectory, - _pFileMonitor->QueryCompletionPort(), - NULL, - 0) == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - // - // Start monitoring - // - hr = Monitor(); - -Finished: - - return hr; -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/precomp.hxx b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/precomp.hxx deleted file mode 100644 index 574244ba35..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/precomp.hxx +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once -#pragma warning( disable : 4091) - -// -// System related headers -// -#define _WINSOCKAPI_ - -#define NTDDI_VERSION 0x06010000 -#define WINVER 0x0601 -#define _WIN32_WINNT 0x0601 - -#include -#include -#include - -//#include -#include -#include - -// This should remove our issue of compiling for win7 without header files. -// We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. -// Then, we set the version defines back to Windows 7 to for the remainder of the compilation. -#undef NTDDI_VERSION -#undef WINVER -#undef _WIN32_WINNT -#define NTDDI_VERSION 0x06020000 -#define WINVER 0x0602 -#define _WIN32_WINNT 0x0602 -#include -#undef NTDDI_VERSION -#undef WINVER -#undef _WIN32_WINNT - -#define NTDDI_VERSION 0x06010000 -#define WINVER 0x0601 -#define _WIN32_WINNT 0x0601 - -#include -#include - -#include -#include -#include - -// -// Option available starting Windows 8. -// 111 is the value in SDK on May 15, 2012. -// -#ifndef WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS -#define WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS 111 -#endif - -#ifdef max -#undef max -template inline T max(T a, T b) -{ - return a > b ? a : b; -} -#endif - -#ifdef min -#undef min -template inline T min(T a, T b) -{ - return a < b ? a : b; -} -#endif - -inline bool IsSpace(char ch) -{ - switch(ch) - { - case 32: // ' ' - case 9: // '\t' - case 10: // '\n' - case 13: // '\r' - case 11: // '\v' - case 12: // '\f' - return true; - default: - return false; - } -} - -#include -#include -#include "stringa.h" -#include "stringu.h" -#include "dbgutil.h" -#include "ahutil.h" -#include "multisz.h" -#include "multisza.h" -#include "base64.h" -#include -#include -#include -#include -#include - -#include "..\..\CommonLib\environmentvariablehash.h" -#include "..\..\CommonLib\aspnetcoreconfig.h" -#include "..\..\CommonLib\hostfxr_utility.h" -#include "..\..\CommonLib\application.h" -#include "..\..\CommonLib\utility.h" -#include "..\..\CommonLib\debugutil.h" -#include "..\..\CommonLib\requesthandler.h" -#include "..\..\CommonLib\resources.h" -#include "..\..\CommonLib\aspnetcore_msg.h" -//#include "aspnetcore_event.h" -#include "appoffline.h" -#include "filewatcher.h" -#include "applicationinfo.h" -#include "applicationmanager.h" -#include "globalmodule.h" -#include "proxymodule.h" -#include "applicationinfo.h" - - -FORCEINLINE -DWORD -WIN32_FROM_HRESULT( - HRESULT hr -) -{ - if ((FAILED(hr)) && - (HRESULT_FACILITY(hr) == FACILITY_WIN32)) - { - return HRESULT_CODE(hr); - } - return hr; -} - -FORCEINLINE -HRESULT -HRESULT_FROM_GETLASTERROR() -{ - return ( GetLastError() != NO_ERROR ) - ? HRESULT_FROM_WIN32( GetLastError() ) - : E_FAIL; -} - -extern PVOID g_pModuleId; -extern BOOL g_fAspnetcoreRHAssemblyLoaded; -extern BOOL g_fAspnetcoreRHLoadedError; -extern BOOL g_fInShutdown; -extern BOOL g_fEnableReferenceCountTracing; -extern DWORD g_dwActiveServerProcesses; -extern HINSTANCE g_hModule; -extern HMODULE g_hAspnetCoreRH; -extern SRWLOCK g_srwLock; -extern PCWSTR g_pwzAspnetcoreRequestHandlerName; -extern HANDLE g_hEventLog; -extern PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication; -extern PFN_ASPNETCORE_CREATE_REQUEST_HANDLER g_pfnAspNetCoreCreateRequestHandler; -#pragma warning( error : 4091) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx deleted file mode 100644 index 8ab880339a..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/src/proxymodule.cxx +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "precomp.hxx" - -__override -HRESULT -ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule( - CHttpModule ** ppModule, - IModuleAllocator * pAllocator -) -{ - ASPNET_CORE_PROXY_MODULE *pModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(); - if (pModule == NULL) - { - return E_OUTOFMEMORY; - } - - *ppModule = pModule; - return S_OK; -} - -__override -VOID -ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate( - VOID -) -/*++ - -Routine description: - - Function called by IIS for global (non-request-specific) notifications - -Arguments: - - None. - -Return value: - - None - ---*/ -{ - /* FORWARDING_HANDLER::StaticTerminate(); - - WEBSOCKET_HANDLER::StaticTerminate();*/ - - ALLOC_CACHE_HANDLER::StaticTerminate(); - - delete this; -} - -ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE( -) : m_pApplicationInfo(NULL), m_pHandler(NULL) -{ -} - -ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE() -{ - if (m_pApplicationInfo != NULL) - { - m_pApplicationInfo->DereferenceApplicationInfo(); - m_pApplicationInfo = NULL; - } - - if (m_pHandler != NULL) - { - m_pHandler->DereferenceRequestHandler(); - m_pHandler = NULL; - } -} - -__override -REQUEST_NOTIFICATION_STATUS -ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( - IHttpContext * pHttpContext, - IHttpEventProvider * -) -{ - HRESULT hr = S_OK; - ASPNETCORE_CONFIG *pConfig = NULL; - APPLICATION_MANAGER *pApplicationManager = NULL; - REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; - APPLICATION* pApplication = NULL; - STACK_STRU(struFileName, 256); - if (g_fInShutdown) - { - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; - } - - hr = ASPNETCORE_CONFIG::GetConfig(g_pHttpServer, g_pModuleId, pHttpContext, g_hEventLog, &pConfig); - if (FAILED(hr)) - { - goto Finished; - } - - pApplicationManager = APPLICATION_MANAGER::GetInstance(); - if (pApplicationManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pApplicationManager->GetOrCreateApplicationInfo( - g_pHttpServer, - pConfig, - &m_pApplicationInfo); - if (FAILED(hr)) - { - goto Finished; - } - - // app_offline check to avoid loading aspnetcorerh.dll unnecessarily - if (m_pApplicationInfo->AppOfflineFound()) - { - // servicing app_offline - HTTP_DATA_CHUNK DataChunk; - IHttpResponse *pResponse = NULL; - APP_OFFLINE_HTM *pAppOfflineHtm = NULL; - - pResponse = pHttpContext->GetResponse(); - pAppOfflineHtm = m_pApplicationInfo->QueryAppOfflineHtm(); - DBG_ASSERT(pAppOfflineHtm); - DBG_ASSERT(pResponse); - - // Ignore failure hresults as nothing we can do - // Set fTrySkipCustomErrors to true as we want client see the offline content - pResponse->SetStatus(503, "Service Unavailable", 0, hr, NULL, TRUE); - pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); - - DataChunk.DataChunkType = HttpDataChunkFromMemory; - DataChunk.FromMemory.pBuffer = (PVOID)pAppOfflineHtm->m_Contents.QueryStr(); - DataChunk.FromMemory.BufferLength = pAppOfflineHtm->m_Contents.QueryCB(); - pResponse->WriteEntityChunkByReference(&DataChunk); - - retVal = RQ_NOTIFICATION_FINISH_REQUEST; - goto Finished; - } - - // make sure assmebly is loaded and application is created - hr = m_pApplicationInfo->EnsureApplicationCreated(); - if (FAILED(hr)) - { - goto Finished; - } - - m_pApplicationInfo->ExtractApplication(&pApplication); - - // make sure application is in running state - // cannot recreate the application as we cannot reload clr for inprocess - if (pApplication != NULL && - pApplication->QueryStatus() != APPLICATION_STATUS::RUNNING && - pApplication->QueryStatus() != APPLICATION_STATUS::STARTING) - { - hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); - goto Finished; - } - - // Create RequestHandler and process the request - hr = m_pApplicationInfo->QueryCreateRequestHandler()(pHttpContext, - (HTTP_MODULE_ID*) &g_pModuleId, - pApplication, - &m_pHandler); - - if (FAILED(hr)) - { - goto Finished; - } - - retVal = m_pHandler->OnExecuteRequestHandler(); - -Finished: - if (FAILED(hr)) - { - retVal = RQ_NOTIFICATION_FINISH_REQUEST; - if (hr == HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)) - { - pHttpContext->GetResponse()->SetStatus(503, "Service Unavailable", 0, hr); - } - else - { - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); - } - } - - if (pApplication != NULL) - { - pApplication->DereferenceApplication(); - } - return retVal; -} - -__override -REQUEST_NOTIFICATION_STATUS -ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion( - IHttpContext *, - DWORD, - BOOL, - IHttpEventProvider *, - IHttpCompletionInfo * pCompletionInfo -) -{ - return m_pHandler->OnAsyncCompletion( - pCompletionInfo->GetCompletionBytes(), - pCompletionInfo->GetCompletionStatus()); -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.cpp new file mode 100644 index 0000000000..12fcb1d436 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.cpp @@ -0,0 +1,4 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// Do not remove this file. It is used for precompiled header generation diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.h b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.h new file mode 100644 index 0000000000..29c485bc9c --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/AspNetCore/stdafx.h @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#pragma warning( disable : 4091) + +// +// System related headers +// +#define WIN32_LEAN_AND_MEAN + +#define NTDDI_VERSION NTDDI_WIN7 +#define WINVER _WIN32_WINNT_WIN7 +#define _WIN32_WINNT 0x0601 + +#include +#include +#include +#include +#include "stringu.h" +#include "stringa.h" + +#pragma warning( error : 4091) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h new file mode 100644 index 0000000000..32706b0192 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "IOutputManager.h" +#include "StdWrapper.h" +#include "EventLog.h" +#include "exceptions.h" +#include "StringHelpers.h" +#include "debugutil.h" + +class BaseOutputManager : + public IOutputManager +{ +public: + BaseOutputManager() : BaseOutputManager(/* fEnableNativeLogging */ true) {} + BaseOutputManager(bool enableNativeLogging) : + m_disposed(false), + stdoutWrapper(nullptr), + stderrWrapper(nullptr), + m_enableNativeRedirection(enableNativeLogging) + { + InitializeSRWLock(&m_srwLock); + } + ~BaseOutputManager() {} + + void + TryStartRedirection() + { + const auto startLambda = [&]() { this->Start(); }; + TryOperation(startLambda, L"Could not start stdout redirection in %s. Exception message: %s."); + } + + void + TryStopRedirection() + { + const auto stopLambda = [&]() { this->Stop(); }; + TryOperation(stopLambda, L"Could not stop stdout redirection in %s. Exception message: %s."); + } + +protected: + std::wstring m_stdOutContent; + bool m_disposed; + bool m_enableNativeRedirection; + SRWLOCK m_srwLock{}; + std::unique_ptr stdoutWrapper; + std::unique_ptr stderrWrapper; + + template + void + TryOperation(Functor func, + std::wstring exceptionMessage) + { + try + { + func(); + } + catch (std::runtime_error& exception) + { + EventLog::Warn(ASPNETCORE_EVENT_GENERAL_WARNING, exceptionMessage.c_str(), GetModuleName().c_str(), to_wide_string(exception.what(), GetConsoleOutputCP()).c_str()); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + } + } +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 1ab1ef971c..2386dfc6ee 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -72,16 +72,20 @@ true + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ true + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ false + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ false C:\AspNetCoreModule\src\IISLib;$(IncludePath) + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ @@ -91,10 +95,16 @@ Disabled false WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h true MultiThreadedDebug false ProgramDatabase + ..\iislib; + true + stdcpp17 + stdafx.h Windows @@ -109,11 +119,17 @@ Disabled false _DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h true ProgramDatabase false MultiThreadedDebug false + ..\iislib; + true + stdcpp17 + stdafx.h Windows @@ -130,13 +146,20 @@ true false WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h true MultiThreaded false + ..\iislib; + true + stdcpp17 + stdafx.h Windows true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true true @@ -151,17 +174,22 @@ true false NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h true - - + ..\iislib; + true MultiThreaded false + stdcpp17 + stdafx.h Windows true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true true @@ -171,32 +199,71 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + + + + - - - + + + + + + + + + + - - + + + + + + Create - Create Create + Create Create - + + + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h new file mode 100644 index 0000000000..0aade37061 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +class ConfigurationLoadException: public std::runtime_error +{ + public: + ConfigurationLoadException(std::wstring msg) + : runtime_error("Configuration load exception has occured"), message(std::move(msg)) + { + } + + std::wstring get_message() const { return message; } + + private: + std::wstring message; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp new file mode 100644 index 0000000000..11e790cff7 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ConfigurationSection.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +std::wstring ConfigurationSection::GetRequiredString(const std::wstring& name) const +{ + auto result = GetString(name); + if (!result.has_value() || result.value().empty()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +bool ConfigurationSection::GetRequiredBool(const std::wstring& name) const +{ + auto result = GetBool(name); + if (!result.has_value()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +DWORD ConfigurationSection::GetRequiredLong(const std::wstring& name) const +{ + auto result = GetLong(name); + if (!result.has_value()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +DWORD ConfigurationSection::GetRequiredTimespan(const std::wstring& name) const +{ + auto result = GetTimespan(name); + if (!result.has_value()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +void ConfigurationSection::ThrowRequiredException(const std::wstring& name) +{ + throw ConfigurationLoadException(format(L"Attribute '%s' is required.", name.c_str())); +} + +std::optional find_element(const std::vector>& pairs, const std::wstring& name) +{ + const auto iter = std::find_if( + pairs.begin(), + pairs.end(), + [&](const std::pair& pair) { return equals_ignore_case(pair.first, name); }); + + if (iter == pairs.end()) + { + return std::nullopt; + } + + return std::make_optional(iter->second); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h new file mode 100644 index 0000000000..f998478db2 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include + +#include "NonCopyable.h" + +#define CS_ASPNETCORE_COLLECTION_ITEM_NAME L"name" +#define CS_ASPNETCORE_COLLECTION_ITEM_VALUE L"value" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLES L"environmentVariables" +#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" +#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" +#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT L"" +#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit" +#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit" +#define CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS L"outofprocess" +#define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess" +#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" +#define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" +#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" +#define CS_ENABLED L"enabled" + +class ConfigurationSection: NonCopyable +{ +public: + ConfigurationSection() = default; + virtual ~ConfigurationSection() = default; + virtual std::optional GetString(const std::wstring& name) const = 0; + virtual std::optional GetBool(const std::wstring& name) const = 0; + virtual std::optional GetLong(const std::wstring& name) const = 0; + virtual std::optional GetTimespan(const std::wstring& name) const = 0; + + std::wstring GetRequiredString(const std::wstring& name) const; + bool GetRequiredBool(const std::wstring& name) const; + DWORD GetRequiredLong(const std::wstring& name) const; + DWORD GetRequiredTimespan(const std::wstring& name) const; + + virtual std::vector> GetKeyValuePairs(const std::wstring& name) const = 0; + +protected: + static void ThrowRequiredException(const std::wstring& name); +}; + +std::optional find_element(const std::vector>& pairs, const std::wstring& name); diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp new file mode 100644 index 0000000000..558d054659 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ConfigurationSource.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +std::shared_ptr ConfigurationSource::GetRequiredSection(const std::wstring& name) const +{ + auto section = GetSection(name); + if (!section) + { + throw ConfigurationLoadException(format(L"Unable to get required configuration section '%s'. Possible reason is web.config authoring error.", name.c_str())); + } + return section; +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h new file mode 100644 index 0000000000..53ab5a5218 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include +#include "NonCopyable.h" +#include "ConfigurationSection.h" + +#define CS_ASPNETCORE_SECTION L"system.webServer/aspNetCore" +#define CS_WINDOWS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/windowsAuthentication" +#define CS_BASIC_AUTHENTICATION_SECTION L"system.webServer/security/authentication/basicAuthentication" +#define CS_ANONYMOUS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/anonymousAuthentication" + +class ConfigurationSource: NonCopyable +{ +public: + ConfigurationSource() = default; + virtual ~ConfigurationSource() = default; + virtual std::shared_ptr GetSection(const std::wstring& name) const = 0; + std::shared_ptr GetRequiredSection(const std::wstring& name) const; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp new file mode 100644 index 0000000000..5e71216ce5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "Environment.h" + +#include + +std::wstring +Environment::ExpandEnvironmentVariables(const std::wstring & str) +{ + DWORD requestedSize = ExpandEnvironmentStringsW(str.c_str(), nullptr, 0); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables"); + } + + std::wstring expandedStr; + do + { + expandedStr.resize(requestedSize); + requestedSize = ExpandEnvironmentStringsW(str.c_str(), expandedStr.data(), requestedSize); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables"); + } + } while (expandedStr.size() != requestedSize); + + // trim null character as ExpandEnvironmentStringsW returns size including null character + expandedStr.resize(requestedSize - 1); + + return expandedStr; +} + +std::optional +Environment::GetEnvironmentVariableValue(const std::wstring & str) +{ + DWORD requestedSize = GetEnvironmentVariableW(str.c_str(), nullptr, 0); + if (requestedSize == 0) + { + if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) + { + return std::nullopt; + } + + throw std::system_error(GetLastError(), std::system_category(), "GetEnvironmentVariableW"); + } + + std::wstring expandedStr; + do + { + expandedStr.resize(requestedSize); + requestedSize = GetEnvironmentVariableW(str.c_str(), expandedStr.data(), requestedSize); + if (requestedSize == 0) + { + throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentStringsW"); + } + } while (expandedStr.size() != requestedSize + 1); + + expandedStr.resize(requestedSize); + + return expandedStr; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h new file mode 100644 index 0000000000..16022e18cf --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/Environment.h @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +class Environment +{ +public: + Environment() = delete; + ~Environment() = delete; + + static + std::wstring ExpandEnvironmentVariables(const std::wstring & str); + static + std::optional GetEnvironmentVariableValue(const std::wstring & str); +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp new file mode 100644 index 0000000000..e5ee8c1708 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include +#include "EventLog.h" +#include "debugutil.h" +#include "exceptions.h" + +extern HANDLE g_hEventLog; + +bool +EventLog::LogEventNoTrace( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + _In_ LPCWSTR pstrMsg +) +{ + if (g_hEventLog == nullptr) + { + return true; + } + // Static locals to avoid getting the process ID and string multiple times. + // Effectively have the same semantics as global variables, except initialized + // on first occurence. + static const auto processIdString = GetProcessIdString(); + static const auto versionInfoString = GetVersionInfoString(); + + std::array eventLogDataStrings + { + pstrMsg, + processIdString.c_str(), + versionInfoString.c_str() + }; + + return ReportEventW(g_hEventLog, + dwEventInfoType, + 0, // wCategory + dwEventId, + NULL, // lpUserSid + static_cast(eventLogDataStrings.size()), // wNumStrings + 0, // dwDataSize, + eventLogDataStrings.data(), + NULL // lpRawData + ); +} + +VOID +EventLog::LogEvent( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + _In_ LPCWSTR pstrMsg +) +{ + LOG_LAST_ERROR_IF(!LogEventNoTrace(dwEventInfoType, dwEventId, pstrMsg)); + + DebugPrintfW(dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, L"Event Log: '%ls' \r\nEnd Event Log Message.", pstrMsg); +} + +VOID +EventLog::LogEventF( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + _In_ LPCWSTR pstrMsg, + _In_ va_list argsList +) +{ + STACK_STRU ( strEventMsg, 256 ); + + if (SUCCEEDED(strEventMsg.SafeVsnwprintf( + pstrMsg, + argsList))) + { + EventLog::LogEvent( + dwEventInfoType, + dwEventId, + strEventMsg.QueryStr()); + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.h new file mode 100644 index 0000000000..4bb72b623a --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventLog.h @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "resources.h" + +#define _va_start(ap, x) \ + __pragma(warning(push)) \ + __pragma(warning(disable:26481 26492)) /*Don't use pointer arithmetic. Don't use const_cast to cast away const.*/ \ + va_start(ap, x) \ + __pragma(warning(pop)) + +#define _va_end(args) \ + __pragma(warning(push)) \ + __pragma(warning(disable:26477)) /*Use 'nullptr' rather than 0 or NULL*/ \ + va_end(args) \ + __pragma(warning(pop)) + +class EventLog +{ +public: + static + VOID + Error( + _In_ DWORD dwEventId, + _In_ PCWSTR pstrMsg, + ...) + { + va_list args; + _va_start(args, pstrMsg); + LogEventF(EVENTLOG_ERROR_TYPE, dwEventId, pstrMsg, args); + _va_end(args); + } + + static + VOID + Info( + _In_ DWORD dwEventId, + _In_ PCWSTR pstrMsg, + ...) + { + va_list args; + _va_start(args, pstrMsg); + LogEventF(EVENTLOG_INFORMATION_TYPE, dwEventId, pstrMsg, args); + _va_end(args); + } + + static + VOID + Warn( + _In_ DWORD dwEventId, + _In_ PCWSTR pstrMsg, + ...) + { + va_list args; + _va_start(args, pstrMsg); + LogEventF(EVENTLOG_WARNING_TYPE, dwEventId, pstrMsg, args); + _va_end(args); + } + + static + bool + LogEventNoTrace( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + _In_ LPCWSTR pstrMsg); + +private: + static + VOID + LogEvent( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + _In_ LPCWSTR pstrMsg + ); + + static + VOID + LogEventF( + _In_ WORD dwEventInfoType, + _In_ DWORD dwEventId, + __in PCWSTR pstrMsg, + va_list argsList + ); +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventTracing.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventTracing.h new file mode 100644 index 0000000000..3734068aae --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/EventTracing.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) + +#include "aspnetcore_event.h" + +#pragma warning( pop ) + +template< class EVENT, typename ...Params > +void RaiseEvent(IHttpTraceContext * pTraceContext,Params&&... params) +{ + if (pTraceContext != nullptr && EVENT::IsEnabled(pTraceContext)) + { + EVENT::RaiseEvent(pTraceContext, std::forward(params)...); + } +} + +template< class EVENT, typename ...Params > +void RaiseEvent(IHttpContext * pHttpContext,Params&&... params) +{ + ::RaiseEvent(pHttpContext->GetTraceContext(), std::forward(params)...); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp new file mode 100644 index 0000000000..1b3772bb4f --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp @@ -0,0 +1,178 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "FileOutputManager.h" +#include "sttimer.h" +#include "exceptions.h" +#include "debugutil.h" +#include "SRWExclusiveLock.h" +#include "file_utility.h" +#include "StdWrapper.h" +#include "StringHelpers.h" + +FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath) : + FileOutputManager(pwzStdOutLogFileName, pwzApplicationPath, /* fEnableNativeLogging */ true) { } + +FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring pwzApplicationPath, bool fEnableNativeLogging) : + BaseOutputManager(fEnableNativeLogging), + m_hLogFileHandle(INVALID_HANDLE_VALUE), + m_applicationPath(pwzApplicationPath), + m_stdOutLogFileName(pwzStdOutLogFileName) +{ + InitializeSRWLock(&m_srwLock); +} + +FileOutputManager::~FileOutputManager() +{ + FileOutputManager::Stop(); +} + +// Start redirecting stdout and stderr into the file handle. +// Uses sttimer to continuously flush output into the file. +void +FileOutputManager::Start() +{ + SYSTEMTIME systemTime; + SECURITY_ATTRIBUTES saAttr = { 0 }; + FILETIME processCreationTime; + FILETIME dummyFileTime; + + // To make Console.* functions work, allocate a console + // in the current process. + if (!AllocConsole()) + { + THROW_LAST_ERROR_IF(GetLastError() != ERROR_ACCESS_DENIED); + } + + // Concatenate the log file name and application path + auto logPath = m_applicationPath / m_stdOutLogFileName; + create_directories(logPath.parent_path()); + + THROW_LAST_ERROR_IF(!GetProcessTimes( + GetCurrentProcess(), + &processCreationTime, + &dummyFileTime, + &dummyFileTime, + &dummyFileTime)); + + THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime)); + + m_logFilePath = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log", + logPath.c_str(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + GetCurrentProcessId()); + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create the file with both READ and WRITE. + m_hLogFileHandle = CreateFileW(m_logFilePath.c_str(), + FILE_READ_DATA | FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + THROW_LAST_ERROR_IF(m_hLogFileHandle == INVALID_HANDLE_VALUE); + + stdoutWrapper = std::make_unique(stdout, STD_OUTPUT_HANDLE, m_hLogFileHandle, m_enableNativeRedirection); + stderrWrapper = std::make_unique(stderr, STD_ERROR_HANDLE, m_hLogFileHandle, m_enableNativeRedirection); + + stdoutWrapper->StartRedirection(); + stderrWrapper->StartRedirection(); +} + +void +FileOutputManager::Stop() +{ + CHAR pzFileContents[MAX_FILE_READ_SIZE] = { 0 }; + DWORD dwNumBytesRead; + LARGE_INTEGER li = { 0 }; + DWORD dwFilePointer = 0; + HANDLE handle = NULL; + WIN32_FIND_DATA fileData; + + if (m_disposed) + { + return; + } + + SRWExclusiveLock lock(m_srwLock); + + if (m_disposed) + { + return; + } + + m_disposed = true; + + if (m_hLogFileHandle == INVALID_HANDLE_VALUE) + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + + FlushFileBuffers(m_hLogFileHandle); + + if (stdoutWrapper != nullptr) + { + THROW_IF_FAILED(stdoutWrapper->StopRedirection()); + } + + if (stderrWrapper != nullptr) + { + THROW_IF_FAILED(stderrWrapper->StopRedirection()); + } + + // delete empty log file + handle = FindFirstFile(m_logFilePath.c_str(), &fileData); + if (handle != INVALID_HANDLE_VALUE && + handle != NULL && + fileData.nFileSizeHigh == 0 && + fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh + { + FindClose(handle); + LOG_LAST_ERROR_IF(!DeleteFile(m_logFilePath.c_str())); + return; + } + + // Read the first 30Kb from the file and store it in a buffer. + // By doing this, we can close the handle to the file and be done with it. + THROW_LAST_ERROR_IF(!GetFileSizeEx(m_hLogFileHandle, &li)); + + if (li.HighPart > 0) + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN); + + THROW_LAST_ERROR_IF(dwFilePointer == INVALID_SET_FILE_POINTER); + + THROW_LAST_ERROR_IF(!ReadFile(m_hLogFileHandle, pzFileContents, MAX_FILE_READ_SIZE, &dwNumBytesRead, NULL)); + + m_stdOutContent = to_wide_string(std::string(pzFileContents, dwNumBytesRead), GetConsoleOutputCP()); + + auto content = GetStdOutContent(); + if (!content.empty()) + { + // printf will fail in in full IIS + if (wprintf(content.c_str()) != -1) + { + // Need to flush contents for the new stdout and stderr + _flushall(); + } + } +} + +std::wstring FileOutputManager::GetStdOutContent() +{ + return m_stdOutContent; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h new file mode 100644 index 0000000000..435e07d4ff --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/FileOutputManager.h @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "sttimer.h" +#include "HandleWrapper.h" +#include "StdWrapper.h" +#include "stringa.h" +#include "stringu.h" +#include "BaseOutputManager.h" + +class FileOutputManager : public BaseOutputManager +{ + #define MAX_FILE_READ_SIZE 30000 +public: + FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName); + FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName, bool fEnableNativeLogging); + ~FileOutputManager(); + + virtual std::wstring GetStdOutContent() override; + void Start() override; + void Stop() override; + +private: + HandleWrapper m_hLogFileHandle; + std::wstring m_stdOutLogFileName; + std::filesystem::path m_applicationPath; + std::filesystem::path m_logFilePath; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp new file mode 100644 index 0000000000..15ce48b8c5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp @@ -0,0 +1,133 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include +#include + +#include "GlobalVersionUtility.h" + +namespace fs = std::filesystem; + +// throws runtime error if no request handler versions are installed. +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::GetGlobalRequestHandlerPath(PCWSTR pwzAspNetCoreFolderPath, PCWSTR pwzHandlerVersion, PCWSTR pwzHandlerName) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw new std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + if (pwzHandlerVersion == NULL) + { + throw new std::invalid_argument("pwzHandlerVersion is NULL"); + } + + if (pwzHandlerName == NULL) + { + throw new std::invalid_argument("pwzHandlerName is NULL"); + } + + std::wstring folderVersion(pwzHandlerVersion); + fs::path aspNetCoreFolderPath(pwzAspNetCoreFolderPath); + + if (folderVersion.empty()) + { + folderVersion = FindHighestGlobalVersion(pwzAspNetCoreFolderPath); + } + + aspNetCoreFolderPath = aspNetCoreFolderPath + .append(folderVersion) + .append(pwzHandlerName); + return aspNetCoreFolderPath; +} + +// Throw filesystem_error if directory_iterator can't iterate over the directory +// Throw invalid_argument if any argument is null +std::vector +GlobalVersionUtility::GetRequestHandlerVersions(PCWSTR pwzAspNetCoreFolderPath) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw new std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + std::vector versionsInDirectory; + for (auto& p : fs::directory_iterator(pwzAspNetCoreFolderPath)) + { + if (!fs::is_directory(p)) + { + continue; + } + + fx_ver_t requested_ver(-1, -1, -1); + if (fx_ver_t::parse(p.path().filename(), &requested_ver, false)) + { + versionsInDirectory.push_back(requested_ver); + } + } + return versionsInDirectory; +} + +// throws runtime error if no request handler versions are installed. +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::FindHighestGlobalVersion(PCWSTR pwzAspNetCoreFolderPath) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + std::vector versionsInDirectory = GetRequestHandlerVersions(pwzAspNetCoreFolderPath); + if (versionsInDirectory.empty()) + { + throw std::runtime_error("Cannot find request handler next to aspnetcorev2.dll. Verify a version of the request handler is installed in a version folder."); + } + std::sort(versionsInDirectory.begin(), versionsInDirectory.end()); + + return versionsInDirectory.back().as_str(); +} + +// Throws std::out_of_range if there is an index out of range +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::RemoveFileNameFromFolderPath(std::wstring fileName) +{ + fs::path path(fileName); + return path.parent_path(); +} + +std::wstring +GlobalVersionUtility::GetModuleName(HMODULE hModuleName) +{ + DWORD dwSize = MAX_PATH; + BOOL fDone = FALSE; + + // Instead of creating a temporary buffer, use the std::wstring directly as the receive buffer. + std::wstring retVal; + retVal.resize(dwSize); + + while (!fDone) + { + DWORD dwReturnedSize = GetModuleFileNameW(hModuleName, &retVal[0], dwSize); + if (dwReturnedSize == 0) + { + throw new std::runtime_error("GetModuleFileNameW returned 0."); + } + else if ((dwReturnedSize == dwSize) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + { + dwSize *= 2; + retVal.resize(dwSize); // smaller buffer. increase the buffer and retry + } + else + { + // GetModuleFilename will not account for the null terminator + // std::wstring auto appends one, so we don't need to subtract 1 when resizing. + retVal.resize(dwReturnedSize); + fDone = TRUE; + } + } + + return retVal; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h new file mode 100644 index 0000000000..7cd8420b5b --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "fx_ver.h" + +class GlobalVersionUtility +{ +public: + + static + std::wstring + GetGlobalRequestHandlerPath(PCWSTR pwzAspNetCoreFolderPath, PCWSTR pwzHandlerVersion, PCWSTR pwzHandlerName); + + static + std::wstring + FindHighestGlobalVersion(PCWSTR pwzAspNetCoreFolderPath); + + static + std::wstring + RemoveFileNameFromFolderPath(std::wstring fileName); + + static + std::vector + GetRequestHandlerVersions(PCWSTR pwzAspNetCoreFolderPath); + + static + std::wstring + GetModuleName(HMODULE hModuleName); +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp new file mode 100644 index 0000000000..4960b06f4e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.cpp @@ -0,0 +1,8 @@ +// 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. + +#include "HandleWrapper.h" + +// Workaround for VC++ bug https://developercommunity.visualstudio.com/content/problem/33928/constexpr-failing-on-nullptr-v141-compiler-regress.html +const HANDLE InvalidHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE; +const HANDLE FindFileHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h new file mode 100644 index 0000000000..56da4ae035 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/HandleWrapper.h @@ -0,0 +1,77 @@ +// 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. + +#pragma once + +#include +#include "ntassert.h" + +struct InvalidHandleTraits +{ + using HandleType = HANDLE; + static const HANDLE DefaultHandle; + static void Close(HANDLE handle) noexcept { CloseHandle(handle); } +}; + +struct NullHandleTraits +{ + using HandleType = HANDLE; + static constexpr HANDLE DefaultHandle = nullptr; + static void Close(HANDLE handle) noexcept { CloseHandle(handle); } +}; + +struct FindFileHandleTraits +{ + using HandleType = HANDLE; + static const HANDLE DefaultHandle; + static void Close(HANDLE handle) noexcept { FindClose(handle); } +}; + +struct ModuleHandleTraits +{ + using HandleType = HMODULE; + static constexpr HMODULE DefaultHandle = nullptr; + static void Close(HMODULE handle) noexcept { FreeModule(handle); } +}; + +// Code analysis doesn't like nullptr usages via traits +#pragma warning( push ) +#pragma warning ( disable : 26477 ) // disable Use 'nullptr' rather than 0 or NULL (es.47). + +template +class HandleWrapper +{ +public: + using HandleType = typename traits::HandleType; + + HandleWrapper(HandleType handle = traits::DefaultHandle) noexcept : m_handle(handle) { } + ~HandleWrapper() + { + if (m_handle != traits::DefaultHandle) + { + traits::Close(m_handle); + } + } + + operator HandleType() noexcept { return m_handle; } + HandleWrapper& operator =(HandleType value) noexcept + { + DBG_ASSERT(m_handle == traits::DefaultHandle); + m_handle = value; + return *this; + } + + HandleType* operator&() noexcept { return &m_handle; } + + HandleType release() noexcept + { + auto value = m_handle; + m_handle = traits::DefaultHandle; + return value; + } + +private: + HandleType m_handle; +}; + +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h new file mode 100644 index 0000000000..4f19ad983d --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/IOutputManager.h @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "stdafx.h" +#include "stringa.h" + +class IOutputManager +{ +public: + virtual + void + Start() = 0; + + virtual + ~IOutputManager() = default; + + virtual + std::wstring + GetStdOutContent() = 0; + + virtual + void + Stop() = 0; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/InvalidOperationException.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/InvalidOperationException.h new file mode 100644 index 0000000000..3963091911 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/InvalidOperationException.h @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include + +class InvalidOperationException: public std::runtime_error +{ + public: + InvalidOperationException(std::wstring msg) + : runtime_error("InvalidOperationException"), message(std::move(msg)) + { + } + + std::wstring as_wstring() const + { + return message; + } + + private: + std::wstring message; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp new file mode 100644 index 0000000000..0a33c4c145 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "LoggingHelpers.h" +#include "FileOutputManager.h" +#include "PipeOutputManager.h" +#include "NullOutputManager.h" +#include "debugutil.h" +#include +#include +#include "ntassert.h" +#include "exceptions.h" +#include "EventLog.h" +#include "BaseOutputManager.h" + +HRESULT +LoggingHelpers::CreateLoggingProvider( + bool fIsLoggingEnabled, + bool fEnableNativeLogging, + PCWSTR pwzStdOutFileName, + PCWSTR pwzApplicationPath, + std::unique_ptr& outputManager +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(outputManager != NULL); + + try + { + // Check if there is an existing active console window before redirecting + // Window == IISExpress with active console window, don't redirect to a pipe + // if true. + CONSOLE_SCREEN_BUFFER_INFO dummy; + + if (fIsLoggingEnabled) + { + auto manager = std::make_unique(pwzStdOutFileName, pwzApplicationPath, fEnableNativeLogging); + outputManager = std::move(manager); + } + else if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &dummy)) + { + outputManager = std::make_unique(fEnableNativeLogging); + } + else + { + outputManager = std::make_unique(); + } + } + catch (std::bad_alloc&) + { + hr = E_OUTOFMEMORY; + } + + return hr; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h new file mode 100644 index 0000000000..eff25b47d1 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "BaseOutputManager.h" + +class LoggingHelpers +{ +public: + + static + HRESULT + CreateLoggingProvider( + bool fLoggingEnabled, + bool fEnableNativeLogging, + PCWSTR pwzStdOutFileName, + PCWSTR pwzApplicationPath, + std::unique_ptr& outputManager + ); +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h new file mode 100644 index 0000000000..cab86d76ea --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "HandleWrapper.h" +#include "exceptions.h" + +extern HMODULE g_hModule; + +class ModuleHelpers +{ +public: + static + void IncrementCurrentModuleRefCount(HandleWrapper &handle) + { + WCHAR path[MAX_PATH]; + +#pragma warning( push ) +#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay + + THROW_LAST_ERROR_IF(!GetModuleFileName(g_hModule, path, MAX_PATH)); + THROW_LAST_ERROR_IF(!GetModuleHandleEx(0, path, &handle)); + +#pragma warning( pop ) + } + + template + static + Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName) { + +#pragma warning( push ) +#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast + auto proc = reinterpret_cast(GetProcAddress(hModule, lpProcName)); +#pragma warning( pop ) + + THROW_LAST_ERROR_IF (!proc); + return proc; + } +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h new file mode 100644 index 0000000000..11f73903fa --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class NonCopyable { +public: + NonCopyable() = default; + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(const NonCopyable&) = delete; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h new file mode 100644 index 0000000000..d9c63b682d --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/NullOutputManager.h @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "stdafx.h" + +class NullOutputManager : public BaseOutputManager +{ +public: + + NullOutputManager() = default; + + ~NullOutputManager() = default; + + void Start() + { + } + + void Stop() + { + } + + std::wstring GetStdOutContent() + { + return L""; + } +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp new file mode 100644 index 0000000000..8a9fe0612e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp @@ -0,0 +1,228 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "PipeOutputManager.h" + +#include "stdafx.h" +#include "Exceptions.h" +#include "SRWExclusiveLock.h" +#include "StdWrapper.h" +#include "ntassert.h" +#include "StringHelpers.h" + +#define LOG_IF_DUPFAIL(err) do { if (err == -1) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0); +#define LOG_IF_ERRNO(err) do { if (err != 0) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0); + +PipeOutputManager::PipeOutputManager() + : PipeOutputManager( /* fEnableNativeLogging */ true) +{ +} + +PipeOutputManager::PipeOutputManager(bool fEnableNativeLogging) : + BaseOutputManager(fEnableNativeLogging), + m_hErrReadPipe(INVALID_HANDLE_VALUE), + m_hErrWritePipe(INVALID_HANDLE_VALUE), + m_hErrThread(nullptr), + m_numBytesReadTotal(0) +{ +} + +PipeOutputManager::~PipeOutputManager() +{ + PipeOutputManager::Stop(); +} + +// Start redirecting stdout and stderr into a pipe +// Continuously read the pipe on a background thread +// until Stop is called. +void PipeOutputManager::Start() +{ + SECURITY_ATTRIBUTES saAttr = { 0 }; + HANDLE hStdErrReadPipe; + HANDLE hStdErrWritePipe; + + // To make Console.* functions work, allocate a console + // in the current process. + if (!AllocConsole()) + { + // ERROR_ACCESS_DENIED means there is a console already present. + if (GetLastError() != ERROR_ACCESS_DENIED) + { + THROW_LAST_ERROR(); + } + } + + THROW_LAST_ERROR_IF(!CreatePipe(&hStdErrReadPipe, &hStdErrWritePipe, &saAttr, 0 /*nSize*/)); + + m_hErrReadPipe = hStdErrReadPipe; + m_hErrWritePipe = hStdErrWritePipe; + + stdoutWrapper = std::make_unique(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, m_enableNativeRedirection); + stderrWrapper = std::make_unique(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, m_enableNativeRedirection); + + LOG_IF_FAILED(stdoutWrapper->StartRedirection()); + LOG_IF_FAILED(stderrWrapper->StartRedirection()); + + // Read the stderr handle on a separate thread until we get 30Kb. + m_hErrThread = CreateThread( + nullptr, // default security attributes + 0, // default stack size + reinterpret_cast(ReadStdErrHandle), + this, // thread function arguments + 0, // default creation flags + nullptr); // receive thread identifier + + THROW_LAST_ERROR_IF_NULL(m_hErrThread); +} + +// Stop redirecting stdout and stderr into a pipe +// This closes the background thread reading from the pipe +// and prints any output that was captured in the pipe. +// If more than 30Kb was written to the pipe, that output will +// be thrown away. +void PipeOutputManager::Stop() +{ + DWORD dwThreadStatus = 0; + + if (m_disposed) + { + return; + } + + SRWExclusiveLock lock(m_srwLock); + + if (m_disposed) + { + return; + } + + m_disposed = true; + + // Both pipe wrappers duplicate the pipe writer handle + // meaning we are fine to close the handle too. + if (m_hErrWritePipe != INVALID_HANDLE_VALUE) + { + // Flush the pipe writer before closing to capture all output + THROW_LAST_ERROR_IF(!FlushFileBuffers(m_hErrWritePipe)); + CloseHandle(m_hErrWritePipe); + m_hErrWritePipe = INVALID_HANDLE_VALUE; + } + + // Tell each pipe wrapper to stop redirecting output and restore the original values + if (stdoutWrapper != nullptr) + { + LOG_IF_FAILED(stdoutWrapper->StopRedirection()); + } + + if (stderrWrapper != nullptr) + { + LOG_IF_FAILED(stderrWrapper->StopRedirection()); + } + + // Forces ReadFile to cancel, causing the read loop to complete. + // Don't check return value as IO may or may not be completed already. + if (m_hErrThread != nullptr) + { + CancelSynchronousIo(m_hErrThread); + } + + // GetExitCodeThread returns 0 on failure; thread status code is invalid. + if (m_hErrThread != nullptr && + !LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) && + dwThreadStatus == STILL_ACTIVE) + { + // Wait for graceful shutdown, i.e., the exit of the background thread or timeout + if (WaitForSingleObject(m_hErrThread, PIPE_OUTPUT_THREAD_TIMEOUT) != WAIT_OBJECT_0) + { + // If the thread is still running, we need kill it first before exit to avoid AV + if (!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) && + dwThreadStatus == STILL_ACTIVE) + { + LOG_WARN(L"Thread reading stdout/err hit timeout, forcibly closing thread."); + TerminateThread(m_hErrThread, STATUS_CONTROL_C_EXIT); + } + } + } + + if (m_hErrThread != nullptr) + { + CloseHandle(m_hErrThread); + m_hErrThread = nullptr; + } + + if (m_hErrReadPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hErrReadPipe); + m_hErrReadPipe = INVALID_HANDLE_VALUE; + } + + // If we captured any output, relog it to the original stdout + // Useful for the IIS Express scenario as it is running with stdout and stderr + m_stdOutContent = to_wide_string(std::string(m_pipeContents, m_numBytesReadTotal), GetConsoleOutputCP()); + + if (!m_stdOutContent.empty()) + { + // printf will fail in in full IIS + if (wprintf(m_stdOutContent.c_str()) != -1) + { + // Need to flush contents for the new stdout and stderr + _flushall(); + } + } +} + +std::wstring PipeOutputManager::GetStdOutContent() +{ + return m_stdOutContent; +} + +void +PipeOutputManager::ReadStdErrHandle( + LPVOID pContext +) +{ + auto pLoggingProvider = static_cast(pContext); + DBG_ASSERT(pLoggingProvider != NULL); + pLoggingProvider->ReadStdErrHandleInternal(); +} + +void +PipeOutputManager::ReadStdErrHandleInternal() +{ + // If ReadFile ever returns false, exit the thread + DWORD dwNumBytesRead = 0; + while (true) + { + // Fill a maximum of MAX_PIPE_READ_SIZE into a buffer. + if (ReadFile(m_hErrReadPipe, + &m_pipeContents[m_numBytesReadTotal], + MAX_PIPE_READ_SIZE - m_numBytesReadTotal, + &dwNumBytesRead, + nullptr)) + { + m_numBytesReadTotal += dwNumBytesRead; + if (m_numBytesReadTotal >= MAX_PIPE_READ_SIZE) + { + break; + } + } + else + { + return; + } + } + + // Using std::string as a wrapper around new char[] so we don't need to call delete + // Also don't allocate on stack as stack size is 128KB by default. + std::string tempBuffer; + tempBuffer.resize(MAX_PIPE_READ_SIZE); + + // After reading the maximum amount of data, keep reading in a loop until Stop is called on the output manager. + while (true) + { + if (!ReadFile(m_hErrReadPipe, tempBuffer.data(), MAX_PIPE_READ_SIZE, &dwNumBytesRead, nullptr)) + { + return; + } + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h new file mode 100644 index 0000000000..3a1a74cd43 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "StdWrapper.h" +#include "stringu.h" +#include "BaseOutputManager.h" + +class PipeOutputManager : public BaseOutputManager +{ + // Timeout to be used if a thread never exits + #define PIPE_OUTPUT_THREAD_TIMEOUT 2000 + + // Max event log message is ~32KB, limit pipe size just below that. + #define MAX_PIPE_READ_SIZE 30000 +public: + PipeOutputManager(); + PipeOutputManager(bool fEnableNativeLogging); + ~PipeOutputManager(); + + void Start() override; + void Stop() override; + std::wstring GetStdOutContent() override; + + // Thread functions + void ReadStdErrHandleInternal(); + + static void ReadStdErrHandle(LPVOID pContext); + +private: + + HANDLE m_hErrReadPipe; + HANDLE m_hErrWritePipe; + HANDLE m_hErrThread; + CHAR m_pipeContents[MAX_PIPE_READ_SIZE] = { 0 }; + DWORD m_numBytesReadTotal; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ResultException.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ResultException.h new file mode 100644 index 0000000000..e6cb8b4b81 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ResultException.h @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class ResultException: public std::runtime_error +{ +public: + ResultException(HRESULT hr, LOCATION_ARGUMENTS_ONLY) : + runtime_error(format("HRESULT 0x%x returned at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY)), + m_hr(hr) + { + } + + HRESULT GetResult() const noexcept { return m_hr; } + +private: + HRESULT m_hr; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.cpp new file mode 100644 index 0000000000..896189c8a8 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.cpp @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "SRWExclusiveLock.h" + +SRWExclusiveLock::SRWExclusiveLock(const SRWLOCK& lock) noexcept + : m_lock(lock) +{ + AcquireSRWLockExclusive(const_cast(&m_lock)); +} + +SRWExclusiveLock::~SRWExclusiveLock() +{ + ReleaseSRWLockExclusive(const_cast(&m_lock)); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.h new file mode 100644 index 0000000000..6c17e13111 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.h @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +class SRWExclusiveLock +{ +public: + SRWExclusiveLock(const SRWLOCK& lock) noexcept; + ~SRWExclusiveLock(); +private: + const SRWLOCK& m_lock; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp deleted file mode 100644 index 3fd0be51f4..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "stdafx.h" -#include "SRWLockWrapper.h" - -SRWLockWrapper::SRWLockWrapper(const SRWLOCK& lock) - : m_lock(lock) -{ - AcquireSRWLockExclusive(const_cast(&m_lock)); -} - -SRWLockWrapper::~SRWLockWrapper() -{ - ReleaseSRWLockExclusive(const_cast(&m_lock)); -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h deleted file mode 100644 index 2ae57cb2f8..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWLockWrapper.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -class SRWLockWrapper -{ -public: - SRWLockWrapper(const SRWLOCK& lock); - ~SRWLockWrapper(); -private: - const SRWLOCK& m_lock; -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.cpp new file mode 100644 index 0000000000..51f88ccbe0 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.cpp @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "SRWSharedLock.h" + +SRWSharedLock::SRWSharedLock(const SRWLOCK& lock) + : m_lock(lock) +{ + AcquireSRWLockShared(const_cast(&m_lock)); +} + +SRWSharedLock::~SRWSharedLock() +{ + ReleaseSRWLockShared(const_cast(&m_lock)); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.h new file mode 100644 index 0000000000..7f1573e27e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/SRWSharedLock.h @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +class SRWSharedLock +{ +public: + SRWSharedLock(const SRWLOCK& lock); + ~SRWSharedLock(); +private: + const SRWLOCK& m_lock; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ServerErrorHandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ServerErrorHandler.h new file mode 100644 index 0000000000..eb493ce722 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/ServerErrorHandler.h @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include "requesthandler.h" +#include "file_utility.h" +#include "Environment.h" + +class ServerErrorHandler : public REQUEST_HANDLER +{ +public: + + ServerErrorHandler(IHttpContext &pContext, USHORT statusCode, USHORT subStatusCode, std::string statusText, HRESULT hr, HINSTANCE moduleInstance, bool disableStartupPage, int page) noexcept + : REQUEST_HANDLER(pContext), + m_pContext(pContext), + m_HR(hr), + m_disableStartupPage(disableStartupPage), + m_page(page), + m_moduleInstance(moduleInstance), + m_statusCode(statusCode), + m_subStatusCode(subStatusCode), + m_statusText(std::move(statusText)) + { + } + + REQUEST_NOTIFICATION_STATUS ExecuteRequestHandler() override + { + static std::string s_html500Page = GetHtml(m_moduleInstance, m_page); + + WriteStaticResponse(m_pContext, s_html500Page, m_HR, m_disableStartupPage); + + return RQ_NOTIFICATION_FINISH_REQUEST; + } + +private: + void WriteStaticResponse(IHttpContext& pContext, std::string &page, HRESULT hr, bool disableStartupErrorPage) const + { + if (disableStartupErrorPage) + { + pContext.GetResponse()->SetStatus(m_statusCode, m_statusText.c_str(), m_subStatusCode, E_FAIL); + return; + } + + HTTP_DATA_CHUNK dataChunk = {}; + IHttpResponse* pResponse = pContext.GetResponse(); + pResponse->SetStatus(m_statusCode, m_statusText.c_str(), m_subStatusCode, hr, nullptr, true); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + dataChunk.DataChunkType = HttpDataChunkFromMemory; + + dataChunk.FromMemory.pBuffer = page.data(); + dataChunk.FromMemory.BufferLength = static_cast(page.size()); + pResponse->WriteEntityChunkByReference(&dataChunk); + } + + static + std::string + GetHtml(HMODULE module, int page) + { + try + { + HRSRC rc = nullptr; + HGLOBAL rcData = nullptr; + std::string data; + const char* pTempData = nullptr; + + THROW_LAST_ERROR_IF_NULL(rc = FindResource(module, MAKEINTRESOURCE(page), RT_HTML)); + THROW_LAST_ERROR_IF_NULL(rcData = LoadResource(module, rc)); + auto const size = SizeofResource(module, rc); + THROW_LAST_ERROR_IF(size == 0); + THROW_LAST_ERROR_IF_NULL(pTempData = static_cast(LockResource(rcData))); + data = std::string(pTempData, size); + + auto additionalErrorLink = Environment::GetEnvironmentVariableValue(L"ANCM_ADDITIONAL_ERROR_PAGE_LINK"); + std::string additionalHtml; + + if (additionalErrorLink.has_value()) + { + additionalHtml = format(" %S and ", additionalErrorLink->c_str(), additionalErrorLink->c_str()); + } + + return format(data, additionalHtml.c_str()); + } + catch (...) + { + OBSERVE_CAUGHT_EXCEPTION(); + return ""; + } + } + + IHttpContext &m_pContext; + HRESULT m_HR; + bool m_disableStartupPage; + int m_page; + HINSTANCE m_moduleInstance; + USHORT m_statusCode; + USHORT m_subStatusCode; + std::string m_statusText; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.cpp new file mode 100644 index 0000000000..809864f06a --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.cpp @@ -0,0 +1,148 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "StdWrapper.h" +#include "exceptions.h" +#include "LoggingHelpers.h" +#include +#include + +StdWrapper::StdWrapper(FILE* stdStream, DWORD stdHandleNumber, HANDLE handleToRedirectTo, BOOL fEnableNativeRedirection) + : m_previousFileDescriptor(0), + m_stdStream(stdStream), + m_stdHandleNumber(stdHandleNumber), + m_enableNativeRedirection(fEnableNativeRedirection), + m_handleToRedirectTo(handleToRedirectTo), + m_redirectedFile(nullptr) +{ +} + +StdWrapper::~StdWrapper() = default; + +// Redirects stdout/stderr to the provided handle. +// Example: +// If the handleToRedirecTo = 0x24 +// Before: +// _fileno(stdout) = 1 +// GetStdHandle(STD_OUTPUT_HANDLE) = 0x20 +// After: +// _fileno(stdout) = 3 +// GetStdHandle(STD_OUTPUT_HANDLE) = 0x28 <- Duplicated from 0x24 +HRESULT +StdWrapper::StartRedirection() +{ + HANDLE stdHandle; + + // In IIS, stdout and stderr are set to null as w3wp is created with DETACHED_PROCESS, + // so fileno(m_stdStream) returns -2. + // Open a null file such that undoing the redirection succeeds and _dup2 works. + // m_previousFileDescriptor will be used for restoring stdout/stderr + if (_fileno(m_stdStream) == -2) + { + freopen_s((FILE**)&m_stdStream, "nul", "w", m_stdStream); + m_previousFileDescriptor = _dup(_fileno(m_stdStream)); + } + else + { + m_previousFileDescriptor = _dup(_fileno(m_stdStream)); + } + + if (!m_enableNativeRedirection) + { + RETURN_LAST_ERROR_IF(!SetStdHandle(m_stdHandleNumber, m_handleToRedirectTo)); + + return S_OK; + } + // After setting the std handle, we need to set stdout/stderr to the current + // output/error handle. + + // Duplicate the handle before opening the handle and associating a file pointer. + // If we don't by calling close on the file, the same handle value will be closed + // multiple times. + // Note, by calling duplicate handle, the new handle returned will have a different value + // than the original, but point to the same underlying file object. + RETURN_LAST_ERROR_IF(!DuplicateHandle( + /* hSourceProcessHandle*/ GetCurrentProcess(), + /* hSourceHandle */ m_handleToRedirectTo, + /* hTargetProcessHandle */ GetCurrentProcess(), + /* lpTargetHandle */&stdHandle, + /* dwDesiredAccess */ 0, // dwDesired is ignored if DUPLICATE_SAME_ACCESS is specified + /* bInheritHandle */ TRUE, + /* dwOptions */ DUPLICATE_SAME_ACCESS)); + + RETURN_LAST_ERROR_IF(!SetStdHandle(m_stdHandleNumber, stdHandle)); + + // _open_osfhandle will associate a filedescriptor with the handle. + const auto fileDescriptor = _open_osfhandle(reinterpret_cast(stdHandle), _O_WTEXT | _O_TEXT); + + if (fileDescriptor == -1) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + m_redirectedFile = _fdopen(fileDescriptor, "w"); + + if (m_redirectedFile == nullptr) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + // Set stdout/stderr to the newly created file. + const auto dup2Result = _dup2(_fileno(m_redirectedFile), _fileno(m_stdStream)); + + if (dup2Result != 0) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + // Removes buffering from the output + if (setvbuf(m_stdStream, nullptr, _IONBF, 0) != 0) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + return S_OK; +} + +// Redirects stdout/stderr back to the original stdout/stderr. +// Note, this will not restore the original handle values returned by GetStdHandle, +// rather a duplicated number. This is because the original handle value is invalid +// due to dup2 closing the file originally in stdout/stderr +HRESULT +StdWrapper::StopRedirection() const +{ + // After setting the std handle, we need to set stdout/stderr to the current + // output/error handle. + FILE * file = _fdopen(m_previousFileDescriptor, "w"); + if (file == nullptr) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + RETURN_LAST_ERROR_IF(!SetStdHandle(m_stdHandleNumber, reinterpret_cast(_get_osfhandle(m_previousFileDescriptor)))); + + if (!m_enableNativeRedirection) + { + return S_OK; + } + + // Set stdout/stderr to the newly created file output. + const auto dup2Result = _dup2(_fileno(file), _fileno(m_stdStream)); + if (dup2Result != 0) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + if (setvbuf(m_stdStream, nullptr, _IONBF, 0) != 0) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + if (fclose(m_redirectedFile) != 0) + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID)); + } + + return S_OK; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.h new file mode 100644 index 0000000000..17373f093e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StdWrapper.h @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +// Wraps stdout/stderr stream, modifying them to redirect to the given handle +class StdWrapper +{ +public: + StdWrapper(FILE* stdStream, DWORD stdHandleNumber, HANDLE handleToRedirectTo, BOOL fEnableNativeRedirection); + ~StdWrapper(); + HRESULT StartRedirection(); + HRESULT StopRedirection() const; + +private: + int m_previousFileDescriptor; + FILE* m_stdStream; + DWORD m_stdHandleNumber; + BOOL m_enableNativeRedirection; + HANDLE m_handleToRedirectTo; + FILE* m_redirectedFile; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp new file mode 100644 index 0000000000..736597b9a5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp @@ -0,0 +1,43 @@ +// 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. + +#include "StringHelpers.h" +#include "exceptions.h" + +bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase) +{ + if (source.length() < suffix.length()) + { + return false; + } + + const auto offset = source.length() - suffix.length(); + return CSTR_EQUAL == CompareStringOrdinal(source.c_str() + offset, static_cast(suffix.length()), suffix.c_str(), static_cast(suffix.length()), ignoreCase); +} + +bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2) +{ + return CSTR_EQUAL == CompareStringOrdinal(s1.c_str(), static_cast(s1.length()), s2.c_str(), static_cast(s2.length()), true); +} + +std::wstring to_wide_string(const std::string &source, const unsigned int codePage) +{ + // MultiByteToWideChar returns 0 on failure, which is also the same return value + // for empty strings. Preemptive return. + if (source.length() == 0) + { + return L""; + } + + std::wstring destination; + + int nChars = MultiByteToWideChar(codePage, 0, source.data(), static_cast(source.length()), NULL, 0); + THROW_LAST_ERROR_IF(nChars == 0); + + destination.resize(nChars); + + nChars = MultiByteToWideChar(codePage, 0, source.data(), static_cast(source.length()), destination.data(), nChars); + THROW_LAST_ERROR_IF(nChars == 0); + + return destination; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h new file mode 100644 index 0000000000..3adc8863a4 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h @@ -0,0 +1,50 @@ +// 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. + +#pragma once + +#include + +[[nodiscard]] +bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase = false); + +[[nodiscard]] +bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2); + +[[nodiscard]] +std::wstring to_wide_string(const std::string &source, const unsigned int codePage); + +template +[[nodiscard]] +std::wstring format(const std::wstring& format, Args ... args) +{ + std::wstring result; + if (!format.empty()) + { + const size_t size = swprintf(nullptr, 0, format.c_str(), args ...); // Extra char for '\0' + result.resize(size); + if (swprintf(result.data(), result.size() + 1, format.c_str(), args ... ) == -1) + { + throw std::system_error(std::error_code(errno, std::system_category())); + } + } + return result; +} + +template +[[nodiscard]] +std::string format(const std::string& format, Args ... args) +{ + std::string result; + if (!format.empty()) + { + const size_t size = snprintf(nullptr, 0, format.c_str(), args ...); // Extra char for '\0' + result.resize(size); + if (snprintf(result.data(), result.size() + 1, format.c_str(), args ... ) == -1) + { + throw std::system_error(std::error_code(errno, std::system_category())); + } + } + return result; +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp new file mode 100644 index 0000000000..7003442f01 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "WebConfigConfigurationSection.h" + +#include "exceptions.h" +#include "ahutil.h" + +std::optional WebConfigConfigurationSection::GetString(const std::wstring& name) const +{ + CComBSTR result; + if (FAILED_LOG(GetElementStringProperty(m_element, name.c_str(), &result.m_str))) + { + return std::nullopt; + } + + return std::make_optional(std::wstring(result)); +} + +std::optional WebConfigConfigurationSection::GetBool(const std::wstring& name) const +{ + bool result; + if (FAILED_LOG(GetElementBoolProperty(m_element, name.c_str(), &result))) + { + return std::nullopt; + } + + return std::make_optional(result); +} + +std::optional WebConfigConfigurationSection::GetLong(const std::wstring& name) const +{ + DWORD result; + if (FAILED_LOG(GetElementDWORDProperty(m_element, name.c_str(), &result))) + { + return std::nullopt; + } + + return std::make_optional(result); +} + +std::optional WebConfigConfigurationSection::GetTimespan(const std::wstring& name) const +{ + ULONGLONG result; + if (FAILED_LOG(GetElementRawTimeSpanProperty(m_element, name.c_str(), &result))) + { + return std::nullopt; + } + + return std::make_optional(static_cast(result / 10000ull)); +} + +std::vector> WebConfigConfigurationSection::GetKeyValuePairs(const std::wstring& name) const +{ + std::vector> pairs; + HRESULT findElementResult; + CComPtr element = nullptr; + CComPtr elementCollection = nullptr; + CComPtr collectionEntry = nullptr; + ENUM_INDEX index{}; + + if (FAILED_LOG(GetElementChildByName(m_element, name.c_str(), &element))) + { + return pairs; + } + + THROW_IF_FAILED(element->get_Collection(&elementCollection)); + THROW_IF_FAILED(findElementResult = FindFirstElement(elementCollection, &index, &collectionEntry)); + + while (findElementResult != S_FALSE) + { + CComBSTR strHandlerName; + if (LOG_IF_FAILED(GetElementStringProperty(collectionEntry, CS_ASPNETCORE_COLLECTION_ITEM_NAME, &strHandlerName.m_str))) + { + ThrowRequiredException(CS_ASPNETCORE_COLLECTION_ITEM_NAME); + } + + CComBSTR strHandlerValue; + if (LOG_IF_FAILED(GetElementStringProperty(collectionEntry, CS_ASPNETCORE_COLLECTION_ITEM_VALUE, &strHandlerValue.m_str))) + { + ThrowRequiredException(CS_ASPNETCORE_COLLECTION_ITEM_VALUE); + } + + pairs.emplace_back(strHandlerName, strHandlerValue); + + collectionEntry.Release(); + + THROW_IF_FAILED(findElementResult = FindNextElement(elementCollection, &index, &collectionEntry)); + } + + return pairs; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h new file mode 100644 index 0000000000..b206c02049 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include "ConfigurationSection.h" + +class WebConfigConfigurationSection: public ConfigurationSection +{ +public: + WebConfigConfigurationSection(IAppHostElement* pElement) + : m_element(pElement) + { + } + + std::optional GetString(const std::wstring& name) const override; + std::optional GetBool(const std::wstring& name) const override; + std::optional GetLong(const std::wstring& name) const override; + std::optional GetTimespan(const std::wstring& name) const override; + std::vector> GetKeyValuePairs(const std::wstring& name) const override; + +private: + CComPtr m_element; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp new file mode 100644 index 0000000000..c433913ec7 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "WebConfigConfigurationSource.h" + +#include "exceptions.h" +#include "WebConfigConfigurationSection.h" + +std::shared_ptr WebConfigConfigurationSource::GetSection(const std::wstring& name) const +{ + const CComBSTR bstrAspNetCoreSection = name.c_str(); + const CComBSTR applicationConfigPath = m_application.GetAppConfigPath(); + + IAppHostElement* sectionElement; + if (LOG_IF_FAILED(m_manager->GetAdminSection(bstrAspNetCoreSection, applicationConfigPath, §ionElement))) + { + return nullptr; + } + return std::make_unique(sectionElement); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h new file mode 100644 index 0000000000..d0c866096b --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include +#include "ConfigurationSection.h" +#include "ConfigurationSource.h" + +class WebConfigConfigurationSource: public ConfigurationSource +{ +public: + WebConfigConfigurationSource(IAppHostAdminManager *pAdminManager, const IHttpApplication &pHttpApplication) noexcept + : m_manager(pAdminManager), + m_application(pHttpApplication) + { + } + + std::shared_ptr GetSection(const std::wstring& name) const override; + +private: + CComPtr m_manager; + const IHttpApplication &m_application; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.cpp deleted file mode 100644 index b68c093266..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "stdafx.h" - -APPLICATION::APPLICATION( - _In_ IHttpServer* pHttpServer, - _In_ ASPNETCORE_CONFIG* pConfig) : - m_cRefs(1), - m_pConfig(pConfig), - m_status(APPLICATION_STATUS::UNKNOWN) -{ - UNREFERENCED_PARAMETER(pHttpServer); -} - -APPLICATION::~APPLICATION() -{ -} - -APPLICATION_STATUS -APPLICATION::QueryStatus() -{ - return m_status; -} - -ASPNETCORE_CONFIG* -APPLICATION::QueryConfig() -{ - return m_pConfig; -} - -VOID -APPLICATION::ReferenceApplication() -const -{ - InterlockedIncrement(&m_cRefs); -} - -VOID -APPLICATION::DereferenceApplication() -const -{ - DBG_ASSERT(m_cRefs != 0); - - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) - { - delete this; - } -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.h index 43c9dafd0c..add050b208 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/application.h @@ -3,51 +3,146 @@ #pragma once -enum APPLICATION_STATUS -{ - UNKNOWN = 0, - STARTING, - RUNNING, - SHUTDOWN, - FAIL -}; +#include +#include "iapplication.h" +#include "ntassert.h" +#include "SRWExclusiveLock.h" +#include "exceptions.h" -class ASPNETCORE_CONFIG; - -class APPLICATION +class APPLICATION : public IAPPLICATION { public: - APPLICATION( - _In_ IHttpServer* pHttpServer, - _In_ ASPNETCORE_CONFIG* pConfig); + // Non-copyable + APPLICATION(const APPLICATION&) = delete; + const APPLICATION& operator=(const APPLICATION&) = delete; + + HRESULT + TryCreateHandler( + _In_ IHttpContext *pHttpContext, + _Outptr_result_maybenull_ IREQUEST_HANDLER **pRequestHandler) override + { + TraceContextScope traceScope(pHttpContext->GetTraceContext()); + *pRequestHandler = nullptr; + + if (m_fStopCalled) + { + return S_FALSE; + } + + return CreateHandler(pHttpContext, pRequestHandler); + } + + virtual + HRESULT + CreateHandler( + _In_ IHttpContext *pHttpContext, + _Outptr_opt_ IREQUEST_HANDLER **pRequestHandler) = 0; + + APPLICATION(const IHttpApplication& pHttpApplication) + : m_fStopCalled(false), + m_cRefs(1), + m_applicationPhysicalPath(pHttpApplication.GetApplicationPhysicalPath()), + m_applicationConfigPath(pHttpApplication.GetAppConfigPath()), + m_applicationId(pHttpApplication.GetApplicationId()) + { + InitializeSRWLock(&m_stateLock); + m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath); + } + + + VOID + Stop(bool fServerInitiated) override + { + SRWExclusiveLock stopLock(m_stateLock); + + if (m_fStopCalled) + { + return; + } + + m_fStopCalled = true; + + StopInternal(fServerInitiated); + } virtual VOID - ShutDown() = 0; - - virtual - VOID - Recycle() = 0; - - virtual - ~APPLICATION(); - - APPLICATION_STATUS - QueryStatus(); - - ASPNETCORE_CONFIG* - QueryConfig(); + StopInternal(bool fServerInitiated) + { + UNREFERENCED_PARAMETER(fServerInitiated); + } VOID - ReferenceApplication() - const; + ReferenceApplication() noexcept override + { + DBG_ASSERT(m_cRefs > 0); + + InterlockedIncrement(&m_cRefs); + } VOID - DereferenceApplication() - const; + DereferenceApplication() noexcept override + { + DBG_ASSERT(m_cRefs > 0); + + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + const std::wstring& + QueryApplicationId() const noexcept + { + return m_applicationId; + } + + const std::wstring& + QueryApplicationPhysicalPath() const noexcept + { + return m_applicationPhysicalPath; + } + + const std::wstring& + QueryApplicationVirtualPath() const noexcept + { + return m_applicationVirtualPath; + } + + const std::wstring& + QueryConfigPath() const noexcept + { + return m_applicationConfigPath; + } protected: - mutable LONG m_cRefs; - volatile APPLICATION_STATUS m_status; - ASPNETCORE_CONFIG* m_pConfig; + SRWLOCK m_stateLock {}; + bool m_fStopCalled; + +private: + mutable LONG m_cRefs; + + std::wstring m_applicationPhysicalPath; + std::wstring m_applicationVirtualPath; + std::wstring m_applicationConfigPath; + std::wstring m_applicationId; + + static std::wstring ToVirtualPath(const std::wstring& configurationPath) + { + auto segments = 0; + auto position = configurationPath.find('/'); + // Skip first 4 segments of config path + while (segments != 3 && position != std::wstring::npos) + { + segments++; + position = configurationPath.find('/', position + 1); + } + + if (position != std::wstring::npos) + { + return configurationPath.substr(position); + } + + return L"/"; + } }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_event.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_event.h new file mode 100644 index 0000000000..d3ed02223a --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_event.h @@ -0,0 +1,1077 @@ + +#ifndef __ASPNETCOREEVENT_H__ +#define __ASPNETCOREEVENT_H__ +/*++ + + Copyright (c) 2005 Microsoft Corporation + + Module Name: + + aspnetcore_event.h + + Abstract: + + Header file has been generated from mof file containing + IIS trace event descriptions + +--*/ + + + +// +// Start of the new provider class WWWServerTraceProvider, +// GUID: {3a2a4e84-4c21-4981-ae10-3fda0d9b0f83} +// Description: IIS: WWW Server +// + +class WWWServerTraceProvider +{ +public: + static + LPCGUID + GetProviderGuid( VOID ) + // return GUID for the current event class + { + static const GUID ProviderGuid = + {0x3a2a4e84,0x4c21,0x4981,{0xae,0x10,0x3f,0xda,0x0d,0x9b,0x0f,0x83}}; + return &ProviderGuid; + }; + enum enumAreaFlags + { + // AspNetCore module events + ANCM = 0x10000 + }; + static + LPCWSTR + TranslateEnumAreaFlagsToString( enum enumAreaFlags EnumValue) + { + switch( (DWORD) EnumValue ) + { + case 0x10000: return L"ANCM"; + } + return NULL; + }; + + static + BOOL + CheckTracingEnabled( + IHttpTraceContext * pHttpTraceContext, + enumAreaFlags AreaFlags, + DWORD dwVerbosity ) + { + HRESULT hr; + HTTP_TRACE_CONFIGURATION TraceConfig; + TraceConfig.pProviderGuid = GetProviderGuid(); + hr = pHttpTraceContext->GetTraceConfiguration( &TraceConfig ); + if ( FAILED( hr ) || !TraceConfig.fProviderEnabled ) + { + return FALSE; + } + if ( TraceConfig.dwVerbosity >= dwVerbosity && + ( TraceConfig.dwAreas == (DWORD) AreaFlags || + ( TraceConfig.dwAreas & (DWORD)AreaFlags ) == (DWORD)AreaFlags ) ) + { + return TRUE; + } + return FALSE; + }; +}; + +// +// Start of the new event class ANCMEvents, +// GUID: {82ADEAD7-12B2-4781-BDCA-5A4B6C757191} +// Description: ANCM runtime events +// + +class ANCMEvents +{ +public: + static + LPCGUID + GetAreaGuid( VOID ) + // return GUID for the current event class + { + static const GUID AreaGuid = + {0x82adead7,0x12b2,0x4781,{0xbd,0xca,0x5a,0x4b,0x6c,0x75,0x71,0x91}}; + return &AreaGuid; + }; + + // + // Event: mof class name ANCMAppStart, + // Description: Start application success + // EventTypeName: ANCM_START_APPLICATION_SUCCESS + // EventType: 1 + // EventLevel: 4 + // + + class ANCM_START_APPLICATION_SUCCESS + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCWSTR pAppDescription + ) + // + // Raise ANCM_START_APPLICATION_SUCCESS Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 1; + Event.pszEventName = L"ANCM_START_APPLICATION_SUCCESS"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"AppDescription"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pAppDescription; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMAppStartFail, + // Description: Start application failed + // EventTypeName: ANCM_START_APPLICATION_FAIL + // EventType: 2 + // EventLevel: 2 + // + + class ANCM_START_APPLICATION_FAIL + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCWSTR pFailureDescription + ) + // + // Raise ANCM_START_APPLICATION_FAIL Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 2; + Event.pszEventName = L"ANCM_START_APPLICATION_FAIL"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 2; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"FailureDescription"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pFailureDescription; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 2 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardStart, + // Description: Start forwarding request + // EventTypeName: ANCM_REQUEST_FORWARD_START + // EventType: 3 + // EventLevel: 4 + // + + class ANCM_REQUEST_FORWARD_START + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_REQUEST_FORWARD_START Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 3; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_START"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardEnd, + // Description: Finish forwarding request + // EventTypeName: ANCM_REQUEST_FORWARD_END + // EventType: 4 + // EventLevel: 4 + // + + class ANCM_REQUEST_FORWARD_END + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_REQUEST_FORWARD_END Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 4; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_END"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMForwardFail, + // Description: Forwarding request failure + // EventTypeName: ANCM_REQUEST_FORWARD_FAIL + // EventType: 5 + // EventLevel: 2 + // + + class ANCM_REQUEST_FORWARD_FAIL + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG ErrorCode + ) + // + // Raise ANCM_REQUEST_FORWARD_FAIL Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 5; + Event.pszEventName = L"ANCM_REQUEST_FORWARD_FAIL"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 2; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"ErrorCode"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &ErrorCode; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 2 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMWinHttpCallBack, + // Description: Receiving callback from WinHttp + // EventTypeName: ANCM_WINHTTP_CALLBACK + // EventType: 6 + // EventLevel: 4 + // + + class ANCM_WINHTTP_CALLBACK + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG InternetStatus + ) + // + // Raise ANCM_WINHTTP_CALLBACK Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 6; + Event.pszEventName = L"ANCM_WINHTTP_CALLBACK"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"InternetStatus"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &InternetStatus; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMExecuteStart, + // Description: Starting inprocess execute request + // EventTypeName: ANCM_INPROC_EXECUTE_REQUEST_START + // EventType: 7 + // EventLevel: 4 + // + + class ANCM_INPROC_EXECUTE_REQUEST_START + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_INPROC_EXECUTE_REQUEST_START Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 7; + Event.pszEventName = L"ANCM_INPROC_EXECUTE_REQUEST_START"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMExecuteEnd, + // Description: Ending inprocess execute request + // EventTypeName: ANCM_INPROC_EXECUTE_REQUEST_COMPLETION + // EventType: 8 + // EventLevel: 5 + // + + class ANCM_INPROC_EXECUTE_REQUEST_COMPLETION + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG requestStatus + ) + // + // Raise ANCM_INPROC_EXECUTE_REQUEST_COMPLETION Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 8; + Event.pszEventName = L"ANCM_INPROC_EXECUTE_REQUEST_COMPLETION"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 5; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"requestStatus"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &requestStatus; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 5 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMAsyncStart, + // Description: Starting inprocess async completion + // EventTypeName: ANCM_INPROC_ASYNC_COMPLETION_START + // EventType: 9 + // EventLevel: 5 + // + + class ANCM_INPROC_ASYNC_COMPLETION_START + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_INPROC_ASYNC_COMPLETION_START Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 9; + Event.pszEventName = L"ANCM_INPROC_ASYNC_COMPLETION_START"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 5; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 5 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMAsyncEnd, + // Description: Ending inprocess async completion + // EventTypeName: ANCM_INPROC_ASYNC_COMPLETION_COMPLETION + // EventType: 10 + // EventLevel: 5 + // + + class ANCM_INPROC_ASYNC_COMPLETION_COMPLETION + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + ULONG requestStatus + ) + // + // Raise ANCM_INPROC_ASYNC_COMPLETION_COMPLETION Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 10; + Event.pszEventName = L"ANCM_INPROC_ASYNC_COMPLETION_COMPLETION"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 5; + Event.cEventItems = 2; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 2 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"requestStatus"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 1 ].pbData = (PBYTE) &requestStatus; + Items[ 1 ].cbData = 4; + Items[ 1 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 5 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMRequestShutdown, + // Description: Inprocess app shutdown + // EventTypeName: ANCM_INPROC_REQUEST_SHUTDOWN + // EventType: 11 + // EventLevel: 4 + // + + class ANCM_INPROC_REQUEST_SHUTDOWN + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_INPROC_REQUEST_SHUTDOWN Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 11; + Event.pszEventName = L"ANCM_INPROC_REQUEST_SHUTDOWN"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMRequestDisconnect, + // Description: Inprocess request disconnect + // EventTypeName: ANCM_INPROC_REQUEST_DISCONNECT + // EventType: 12 + // EventLevel: 4 + // + + class ANCM_INPROC_REQUEST_DISCONNECT + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_INPROC_REQUEST_DISCONNECT Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 12; + Event.pszEventName = L"ANCM_INPROC_REQUEST_DISCONNECT"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMManagedRequestCompletion, + // Description: Indicate managed request complete + // EventTypeName: ANCM_INPROC_MANAGED_REQUEST_COMPLETION + // EventType: 13 + // EventLevel: 4 + // + + class ANCM_INPROC_MANAGED_REQUEST_COMPLETION + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId + ) + // + // Raise ANCM_INPROC_MANAGED_REQUEST_COMPLETION Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 13; + Event.pszEventName = L"ANCM_INPROC_MANAGED_REQUEST_COMPLETION"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 4; + Event.cEventItems = 1; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 1 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 4 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMHRESULTFailed, + // Description: Failed HRESULT + // EventTypeName: ANCM_HRESULT_FAILED + // EventType: 14 + // EventLevel: 3 + // + + class ANCM_HRESULT_FAILED + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCSTR pFile, + ULONG Line, + ULONG HResult + ) + // + // Raise ANCM_HRESULT_FAILED Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 14; + Event.pszEventName = L"ANCM_HRESULT_FAILED"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 3; + Event.cEventItems = 4; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 4 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"File"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pFile; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : (1 + (DWORD) strlen( (PSTR) Items[ 1 ].pbData ) ); + Items[ 1 ].pszDataDescription = NULL; + Items[ 2 ].pszName = L"Line"; + Items[ 2 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 2 ].pbData = (PBYTE) &Line; + Items[ 2 ].cbData = 4; + Items[ 2 ].pszDataDescription = NULL; + Items[ 3 ].pszName = L"HResult"; + Items[ 3 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 3 ].pbData = (PBYTE) &HResult; + Items[ 3 ].cbData = 4; + Items[ 3 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 3 ); //Verbosity + }; + }; + // + // Event: mof class name ANCMExceptionCaughFailed, + // Description: Caught exception + // EventTypeName: ANCM_EXCEPTION_CAUGHT + // EventType: 15 + // EventLevel: 3 + // + + class ANCM_EXCEPTION_CAUGHT + { + public: + static + HRESULT + RaiseEvent( + IHttpTraceContext * pHttpTraceContext, + LPCGUID pContextId, + LPCSTR pFile, + ULONG Line, + LPCSTR pDescription + ) + // + // Raise ANCM_EXCEPTION_CAUGHT Event + // + { + HTTP_TRACE_EVENT Event; + Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); + Event.dwArea = WWWServerTraceProvider::ANCM; + Event.pAreaGuid = ANCMEvents::GetAreaGuid(); + Event.dwEvent = 15; + Event.pszEventName = L"ANCM_EXCEPTION_CAUGHT"; + Event.dwEventVersion = 1; + Event.dwVerbosity = 3; + Event.cEventItems = 4; + Event.pActivityGuid = NULL; + Event.pRelatedActivityGuid = NULL; + Event.dwTimeStamp = 0; + Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; + + // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS + + HTTP_TRACE_EVENT_ITEM Items[ 4 ]; + Items[ 0 ].pszName = L"ContextId"; + Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) + Items[ 0 ].pbData = (PBYTE) pContextId; + Items[ 0 ].cbData = 16; + Items[ 0 ].pszDataDescription = NULL; + Items[ 1 ].pszName = L"File"; + Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCSTR; // mof type (string) + Items[ 1 ].pbData = (PBYTE) pFile; + Items[ 1 ].cbData = + ( Items[ 1 ].pbData == NULL )? 0 : (1 + (DWORD) strlen( (PSTR) Items[ 1 ].pbData ) ); + Items[ 1 ].pszDataDescription = NULL; + Items[ 2 ].pszName = L"Line"; + Items[ 2 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) + Items[ 2 ].pbData = (PBYTE) &Line; + Items[ 2 ].cbData = 4; + Items[ 2 ].pszDataDescription = NULL; + Items[ 3 ].pszName = L"Description"; + Items[ 3 ].dwDataType = HTTP_TRACE_TYPE_LPCSTR; // mof type (string) + Items[ 3 ].pbData = (PBYTE) pDescription; + Items[ 3 ].cbData = + ( Items[ 3 ].pbData == NULL )? 0 : (1 + (DWORD) strlen( (PSTR) Items[ 3 ].pbData ) ); + Items[ 3 ].pszDataDescription = NULL; + Event.pEventItems = Items; + pHttpTraceContext->RaiseTraceEvent( &Event ); + return S_OK; + }; + + static + BOOL + IsEnabled( + IHttpTraceContext * pHttpTraceContext ) + // Check if tracing for this event is enabled + { + return WWWServerTraceProvider::CheckTracingEnabled( + pHttpTraceContext, + WWWServerTraceProvider::ANCM, + 3 ); //Verbosity + }; + }; +}; +#endif \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index 96cf5fec0c..547192d0df 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -38,12 +38,6 @@ Language=English %1 . -Messageid=1002 -SymbolicName=ASPNETCORE_EVENT_PROCESS_CRASH -Language=English -%1 -. - Messageid=1003 SymbolicName=ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED Language=English @@ -110,42 +104,12 @@ Language=English %1 . -Messageid=1014 -SymbolicName=ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP -Language=English -%1 -. - -Messageid=1015 -SymbolicName=ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING -Language=English -%1 -. - -Messageid=1016 -SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND -Language=English -%1 -. - -Messageid=1017 -SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND -Language=English -%1 -. - Messageid=1018 SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION Language=English %1 . -Messageid=1019 -SymbolicName=ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND -Language=English -%1 -. - Messageid=1020 SymbolicName=ASPNETCORE_EVENT_PROCESS_START_FAILURE Language=English @@ -164,32 +128,26 @@ Language=English %1 . -Messageid=1023 -SymbolicName=ASPNETCORE_EVENT_APP_IN_SHUTDOWN -Language=English -%1 -. - Messageid=1024 -SymbolicName=ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED +SymbolicName=ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR Language=English %1 . Messageid=1025 -SymbolicName=ASPNETCORE_EVENT_GENERAL_INFO_MSG +SymbolicName=ASPNETCORE_EVENT_GENERAL_INFO Language=English %1 . Messageid=1026 -SymbolicName=ASPNETCORE_EVENT_GENERAL_WARNING_MSG +SymbolicName=ASPNETCORE_EVENT_GENERAL_WARNING Language=English %1 . Messageid=1027 -SymbolicName=ASPNETCORE_EVENT_GENERAL_ERROR_MSG +SymbolicName=ASPNETCORE_EVENT_GENERAL_ERROR Language=English %1 . @@ -212,6 +170,44 @@ Language=English %1 . +Messageid=1031 +SymbolicName=ASPNETCORE_EVENT_INPROCESS_START_ERROR +Language=English +%1 +. + +Messageid=1032 +SymbolicName=ASPNETCORE_EVENT_INPROCESS_START_SUCCESS +Language=English +%1 +. + + +Messageid=1033 +SymbolicName=ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL +Language=English +%1 +. + +Messageid=1034 +SymbolicName=ASPNETCORE_CONFIGURATION_LOAD_ERROR +Language=English +%1 +. + +Messageid=1035 +SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT +Language=English +%1 +. + +Messageid=1036 +SymbolicName=ASPNETCORE_EVENT_DEBUG_LOG +Language=English +%1 +. + + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc deleted file mode 100644 index 0abcb0fa2c..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.rc +++ /dev/null @@ -1,2 +0,0 @@ -LANGUAGE 0x9,0x1 -1 11 "MSG00001.bin" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/config_utility.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/config_utility.h new file mode 100644 index 0000000000..ae4aeb0466 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/config_utility.h @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include "ahutil.h" +#include "stringu.h" +#include "exceptions.h" +#include "atlbase.h" + +class ConfigUtility +{ + #define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" + #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" + #define CS_ASPNETCORE_DEBUG_FILE L"debugFile" + #define CS_ASPNETCORE_DEBUG_LEVEL L"debugLevel" + #define CS_ASPNETCORE_HANDLER_SETTINGS_NAME L"name" + #define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE L"value" + +public: + static + HRESULT + FindHandlerVersion(IAppHostElement* pElement, STRU& strHandlerVersionValue) + { + return FindKeyValuePair(pElement, CS_ASPNETCORE_HANDLER_VERSION, strHandlerVersionValue); + } + + static + HRESULT + FindDebugFile(IAppHostElement* pElement, STRU& strDebugFile) + { + return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_FILE, strDebugFile); + } + + static + HRESULT + FindDebugLevel(IAppHostElement* pElement, STRU& strDebugFile) + { + return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_LEVEL, strDebugFile); + } + +private: + static + HRESULT + FindKeyValuePair(IAppHostElement* pElement, PCWSTR key, STRU& strHandlerVersionValue) + { + HRESULT hr; + CComPtr pHandlerSettings = nullptr; + CComPtr pHandlerSettingsCollection = nullptr; + CComPtr pHandlerVar = nullptr; + ENUM_INDEX index{}; + STRU strHandlerName; + STRU strHandlerValue; + + // backwards complatibility with systems not having schema for handlerSettings + if (FAILED_LOG(GetElementChildByName(pElement, CS_ASPNETCORE_HANDLER_SETTINGS, &pHandlerSettings))) + { + return S_OK; + } + + RETURN_IF_FAILED(pHandlerSettings->get_Collection(&pHandlerSettingsCollection)); + + RETURN_IF_FAILED(hr = FindFirstElement(pHandlerSettingsCollection, &index, &pHandlerVar)); + + while (hr != S_FALSE) + { + RETURN_IF_FAILED(GetElementStringProperty(pHandlerVar, CS_ASPNETCORE_HANDLER_SETTINGS_NAME, &strHandlerName)); + RETURN_IF_FAILED(GetElementStringProperty(pHandlerVar, CS_ASPNETCORE_HANDLER_SETTINGS_VALUE, &strHandlerValue)); + + if (strHandlerName.Equals(key, TRUE)) + { + RETURN_IF_FAILED(strHandlerVersionValue.Copy(strHandlerValue)); + break; + } + + strHandlerName.Reset(); + strHandlerValue.Reset(); + pHandlerVar.Release(); + + RETURN_IF_FAILED(hr = FindNextElement(pHandlerSettingsCollection, &index, &pHandlerVar)); + } + + return S_OK; + } +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp new file mode 100644 index 0000000000..3492be7354 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp @@ -0,0 +1,437 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "debugutil.h" + +#include +#include +#include "dbgutil.h" +#include "stringu.h" +#include "stringa.h" +#include "Environment.h" +#include "SRWExclusiveLock.h" +#include "exceptions.h" +#include "atlbase.h" +#include "config_utility.h" +#include "StringHelpers.h" +#include "aspnetcore_msg.h" +#include "EventLog.h" + +inline HANDLE g_logFile = INVALID_HANDLE_VALUE; +inline HMODULE g_hModule; +inline SRWLOCK g_logFileLock; +inline HANDLE g_stdOutHandle = INVALID_HANDLE_VALUE; + +HRESULT +PrintDebugHeader() +{ + // Major, minor are stored in dwFileVersionMS field and patch, build in dwFileVersionLS field as pair of 32 bit numbers + LOG_INFOF(L"Initializing logs for '%ls'. %ls. %ls.", + GetModuleName().c_str(), + GetProcessIdString().c_str(), + GetVersionInfoString().c_str() + ); + + return S_OK; +} + +std::wstring +GetProcessIdString() +{ + return format(L"Process Id: %u.", GetCurrentProcessId()); +} + +std::wstring +GetVersionInfoString() +{ + auto func = [](std::wstring& res) + { + DWORD verHandle = 0; + UINT size = 0; + LPVOID lpBuffer = NULL; + + auto path = GetModuleName(); + + DWORD verSize = GetFileVersionInfoSize(path.c_str(), &verHandle); + RETURN_LAST_ERROR_IF(verSize == 0); + + // Allocate memory to hold data structure returned by GetFileVersionInfo + std::vector verData(verSize); + + RETURN_LAST_ERROR_IF(!GetFileVersionInfo(path.c_str(), verHandle, verSize, verData.data())); + RETURN_LAST_ERROR_IF(!VerQueryValue(verData.data(), L"\\", &lpBuffer, &size)); + + auto verInfo = reinterpret_cast(lpBuffer); + if (verInfo->dwSignature != VS_FFI_SIGNATURE) + { + RETURN_IF_FAILED(E_FAIL); + } + + LPVOID pvProductName = NULL; + unsigned int iProductNameLen = 0; + RETURN_LAST_ERROR_IF(!VerQueryValue(verData.data(), _T("\\StringFileInfo\\040904b0\\FileDescription"), &pvProductName, &iProductNameLen)); + + res = format(L"File Version: %d.%d.%d.%d. Description: %s", + (verInfo->dwFileVersionMS >> 16) & 0xffff, + (verInfo->dwFileVersionMS >> 0) & 0xffff, + (verInfo->dwFileVersionLS >> 16) & 0xffff, + (verInfo->dwFileVersionLS >> 0) & 0xffff, + pvProductName); + return S_OK; + }; + + std::wstring versionInfoString; + + return func(versionInfoString) == S_OK ? versionInfoString : L""; +} + +std::wstring +GetModuleName() +{ + WCHAR path[MAX_PATH]; + LOG_LAST_ERROR_IF(!GetModuleFileName(g_hModule, path, sizeof(path))); + return path; +} + +void SetDebugFlags(const std::wstring &debugValue) +{ + try + { + std::wstringstream stringStream(debugValue); + std::wstring flag; + + while (std::getline(stringStream, flag, L',')) + { + if (_wcsnicmp(flag.c_str(), L"error", wcslen(L"error")) == 0) DEBUG_FLAGS_VAR |= DEBUG_FLAGS_ERROR; + if (_wcsnicmp(flag.c_str(), L"warning", wcslen(L"warning")) == 0) DEBUG_FLAGS_VAR |= DEBUG_FLAGS_WARN; + if (_wcsnicmp(flag.c_str(), L"info", wcslen(L"info")) == 0) DEBUG_FLAGS_VAR |= DEBUG_FLAGS_INFO; + if (_wcsnicmp(flag.c_str(), L"trace", wcslen(L"trace")) == 0) DEBUG_FLAGS_VAR |= DEBUG_FLAGS_TRACE; + if (_wcsnicmp(flag.c_str(), L"console", wcslen(L"console")) == 0) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_CONSOLE; + if (_wcsnicmp(flag.c_str(), L"file", wcslen(L"file")) == 0) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_FILE; + if (_wcsnicmp(flag.c_str(), L"eventlog", wcslen(L"eventlog")) == 0) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_EVENTLOG; + } + + // If file or console is enabled but level is not set, enable levels up to info + if (DEBUG_FLAGS_VAR != 0 && (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ANY) == 0) + { + DEBUG_FLAGS_VAR |= DEBUG_FLAGS_INFO; + } + } + catch (...) + { + // ignore + } +} + +bool CreateDebugLogFile(const std::wstring &debugOutputFile) +{ + try + { + if (!debugOutputFile.empty()) + { + if (g_logFile != INVALID_HANDLE_VALUE) + { + LOG_INFOF(L"Switching debug log files to '%ls'", debugOutputFile.c_str()); + } + + SRWExclusiveLock lock(g_logFileLock); + if (g_logFile != INVALID_HANDLE_VALUE) + { + CloseHandle(g_logFile); + g_logFile = INVALID_HANDLE_VALUE; + } + g_logFile = CreateFileW(debugOutputFile.c_str(), + (GENERIC_READ | GENERIC_WRITE), + (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), + nullptr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + return true; + } + } + catch (...) + { + // ignore + } + + return false; +} + +VOID +DebugInitialize(HMODULE hModule) +{ + g_hModule = hModule; + DuplicateHandle( + /* hSourceProcessHandle*/ GetCurrentProcess(), + /* hSourceHandle */ GetStdHandle(STD_OUTPUT_HANDLE), + /* hTargetProcessHandle */ GetCurrentProcess(), + /* lpTargetHandle */&g_stdOutHandle, + /* dwDesiredAccess */ 0, // dwDesired is ignored if DUPLICATE_SAME_ACCESS is specified + /* bInheritHandle */ TRUE, + /* dwOptions */ DUPLICATE_SAME_ACCESS); + + HKEY hKey; + InitializeSRWLock(&g_logFileLock); + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType; + DWORD dwData; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DebugFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + DEBUG_FLAGS_VAR = dwData; + } + + RegCloseKey(hKey); + } + + try + { + SetDebugFlags(Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG").value_or(L"")); + } + catch (...) + { + // ignore + } + + try + { + const auto debugOutputFile = Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG_FILE"); + + CreateDebugLogFile(debugOutputFile.value_or(L"")); + } + catch (...) + { + // ignore + } + + if (IsDebuggerPresent()) + { + DEBUG_FLAGS_VAR |= DEBUG_FLAGS_INFO; + } + + PrintDebugHeader(); +} + +HRESULT +DebugInitializeFromConfig(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication) +{ + auto oldFlags = DEBUG_FLAGS_VAR; + + CComPtr pAspNetCoreElement; + + const CComBSTR bstrAspNetCoreSection = L"system.webServer/aspNetCore"; + CComBSTR bstrConfigPath = pHttpApplication.GetAppConfigPath(); + + RETURN_IF_FAILED(pHttpServer.GetAdminManager()->GetAdminSection(bstrAspNetCoreSection, + bstrConfigPath, + &pAspNetCoreElement)); + + STRU debugFile; + RETURN_IF_FAILED(ConfigUtility::FindDebugFile(pAspNetCoreElement, debugFile)); + + STRU debugValue; + RETURN_IF_FAILED(ConfigUtility::FindDebugLevel(pAspNetCoreElement, debugValue)); + + SetDebugFlags(debugValue.QueryStr()); + + if (debugFile.QueryCCH() == 0 && IsEnabled(ASPNETCORE_DEBUG_FLAG_FILE)) + { + debugFile.Append(L".\\aspnetcore-debug.log"); + } + + std::filesystem::path filePath = std::filesystem::path(debugFile.QueryStr()); + if (!filePath.empty() && filePath.is_relative()) + { + filePath = std::filesystem::path(pHttpApplication.GetApplicationPhysicalPath()) / filePath; + } + + const auto reopenedFile = CreateDebugLogFile(filePath); + + // Print header if flags changed + if (oldFlags != DEBUG_FLAGS_VAR || reopenedFile) + { + PrintDebugHeader(); + } + + return S_OK; +} + +VOID +DebugStop() +{ + if (g_logFile != INVALID_HANDLE_VALUE) + { + CloseHandle(g_logFile); + } + + if (g_stdOutHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(g_stdOutHandle); + } +} + +BOOL +IsEnabled( + DWORD dwFlag + ) +{ + return ( dwFlag & DEBUG_FLAGS_VAR ); +} + +void WriteFileEncoded(UINT codePage, HANDLE hFile, const LPCWSTR szString) +{ + DWORD nBytesWritten = 0; + auto const encodedByteCount = WideCharToMultiByte(codePage, 0, szString, -1, nullptr, 0, nullptr, nullptr); + auto encodedBytes = std::shared_ptr(new CHAR[encodedByteCount]); + WideCharToMultiByte(codePage, 0, szString, -1, encodedBytes.get(), encodedByteCount, nullptr, nullptr); + + WriteFile(hFile, encodedBytes.get(), encodedByteCount - 1, &nBytesWritten, nullptr); +} + +VOID +DebugPrintW( + DWORD dwFlag, + const LPCWSTR szString + ) +{ + STACK_STRU (strOutput, 256); + HRESULT hr = S_OK; + + if ( IsEnabled( dwFlag ) ) + { + hr = strOutput.SafeSnwprintf( + L"[%S] %s\r\n", + DEBUG_LABEL_VAR, szString ); + + if (FAILED (hr)) + { + return; + } + + OutputDebugString( strOutput.QueryStr() ); + + if (IsEnabled(ASPNETCORE_DEBUG_FLAG_CONSOLE) || g_logFile != INVALID_HANDLE_VALUE) + { + if (IsEnabled(ASPNETCORE_DEBUG_FLAG_CONSOLE)) + { + WriteFileEncoded(GetConsoleOutputCP(), g_stdOutHandle, strOutput.QueryStr()); + } + + if (g_logFile != INVALID_HANDLE_VALUE) + { + SRWExclusiveLock lock(g_logFileLock); + + SetFilePointer(g_logFile, 0, nullptr, FILE_END); + WriteFileEncoded(CP_UTF8, g_logFile, strOutput.QueryStr()); + FlushFileBuffers(g_logFile); + } + } + + if (IsEnabled(ASPNETCORE_DEBUG_FLAG_EVENTLOG)) + { + WORD eventType; + switch (dwFlag) + { + case ASPNETCORE_DEBUG_FLAG_ERROR: + eventType = EVENTLOG_ERROR_TYPE; + break; + case ASPNETCORE_DEBUG_FLAG_WARNING: + eventType = EVENTLOG_WARNING_TYPE; + break; + default: + eventType = EVENTLOG_INFORMATION_TYPE; + break; + } + EventLog::LogEventNoTrace(eventType, ASPNETCORE_EVENT_DEBUG_LOG, strOutput.QueryStr()); + } + } +} + +VOID +DebugPrintfW( + DWORD dwFlag, + const LPCWSTR szFormat, + ... + ) +{ + STACK_STRU (strCooked,256); + + va_list args; + HRESULT hr = S_OK; + + if ( IsEnabled( dwFlag ) ) + { + va_start( args, szFormat ); + + hr = strCooked.SafeVsnwprintf(szFormat, args ); + + va_end( args ); + + if (FAILED (hr)) + { + return; + } + + DebugPrintW( dwFlag, strCooked.QueryStr() ); + } +} + +VOID +DebugPrint( + DWORD dwFlag, + const LPCSTR szString + ) +{ + STACK_STRU (strOutput, 256); + + if ( IsEnabled( dwFlag ) ) + { + strOutput.CopyA(szString); + DebugPrintW(dwFlag, strOutput.QueryStr()); + } +} + +VOID +DebugPrintf( + DWORD dwFlag, + const LPCSTR szFormat, + ... + ) +{ + STACK_STRA (strCooked,256); + + va_list args; + HRESULT hr = S_OK; + + if ( IsEnabled( dwFlag ) ) + { + va_start( args, szFormat ); + + hr = strCooked.SafeVsnprintf(szFormat, args ); + + va_end( args ); + + if (FAILED (hr)) + { + return; + } + + DebugPrint( dwFlag, strCooked.QueryStr() ); + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.h index 16fce88edd..24446dbd80 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/debugutil.h @@ -2,80 +2,78 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once -#define ASPNETCORE_DEBUG_FLAG_INFO 0x00000001 -#define ASPNETCORE_DEBUG_FLAG_WARNING 0x00000002 -#define ASPNETCORE_DEBUG_FLAG_ERROR 0x00000004 -extern DWORD g_dwAspNetCoreDebugFlags; +#include "stdafx.h" +#include "stringu.h" +#include +#include "dbgutil.h" + +#define ASPNETCORE_DEBUG_FLAG_TRACE DEBUG_FLAG_TRACE +#define ASPNETCORE_DEBUG_FLAG_INFO DEBUG_FLAG_INFO +#define ASPNETCORE_DEBUG_FLAG_WARNING DEBUG_FLAG_WARN +#define ASPNETCORE_DEBUG_FLAG_ERROR DEBUG_FLAG_ERROR +#define ASPNETCORE_DEBUG_FLAG_CONSOLE 0x00000010 +#define ASPNETCORE_DEBUG_FLAG_FILE 0x00000020 +#define ASPNETCORE_DEBUG_FLAG_EVENTLOG 0x00000040 + +#define LOG_TRACE(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_TRACE, __VA_ARGS__) +#define LOG_TRACEF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_TRACE, __VA_ARGS__) + +#define LOG_INFO(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) +#define LOG_INFOF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) + +#define LOG_WARN(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) +#define LOG_WARNF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) + +#define LOG_ERROR(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) +#define LOG_ERRORF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) + +VOID +DebugInitialize(HMODULE hModule); + +HRESULT +DebugInitializeFromConfig(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication); + +VOID +DebugStop(); -static BOOL -IfDebug( +IsEnabled( DWORD dwFlag - ) -{ - return ( dwFlag & g_dwAspNetCoreDebugFlags ); -} + ); + +VOID +DebugPrintW( + DWORD dwFlag, + LPCWSTR szString + ); + +VOID +DebugPrintfW( + DWORD dwFlag, + LPCWSTR szFormat, + ... + ); + -static VOID DebugPrint( DWORD dwFlag, LPCSTR szString - ) -{ - STACK_STRA (strOutput, 256); - HRESULT hr = S_OK; + ); - if ( IfDebug( dwFlag ) ) - { - hr = strOutput.SafeSnprintf( - "[aspnetcore.dll] %s\r\n", - szString ); - - if (FAILED (hr)) - { - goto Finished; - } - - OutputDebugStringA( strOutput.QueryStr() ); - } - -Finished: - - return; -} - -static VOID DebugPrintf( -DWORD dwFlag, -LPCSTR szFormat, -... -) -{ - STACK_STRA (strCooked,256); + DWORD dwFlag, + LPCSTR szFormat, + ... + ); - va_list args; - HRESULT hr = S_OK; +std::wstring +GetProcessIdString(); - if ( IfDebug( dwFlag ) ) - { - va_start( args, szFormat ); - - hr = strCooked.SafeVsnprintf(szFormat, args ); - - va_end( args ); - - if (FAILED (hr)) - { - goto Finished; - } - - DebugPrint( dwFlag, strCooked.QueryStr() ); - } - -Finished: - return; -} +std::wstring +GetVersionInfoString(); +std::wstring +GetModuleName(); diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/exceptions.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/exceptions.h new file mode 100644 index 0000000000..18115c0e48 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/exceptions.h @@ -0,0 +1,216 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +#include "debugutil.h" +#include "StringHelpers.h" +#include "InvalidOperationException.h" +#include "ntassert.h" +#include "NonCopyable.h" +#include "EventTracing.h" + +#define LOCATION_INFO_ENABLED TRUE + +#if LOCATION_INFO_ENABLED +#define LOCATION_FORMAT "%s:%d " +#define LOCATION_ARGUMENTS_ONLY _In_opt_ PCSTR fileName, unsigned int lineNumber +#define LOCATION_ARGUMENTS LOCATION_ARGUMENTS_ONLY, +#define LOCATION_CALL_ONLY fileName, lineNumber +#define LOCATION_CALL LOCATION_CALL_ONLY, +#define LOCATION_INFO __FILE__, __LINE__ +#else +#define LOCATION_FORMAT +#define LOCATION_ARGUMENTS_ONLY +#define LOCATION_ARGUMENTS +#define LOCATION_CALL_ONLY +#define LOCATION_CALL +#define LOCATION_INFO +#endif + +#define OBSERVE_CAUGHT_EXCEPTION() CaughtExceptionHResult(LOCATION_INFO); +#define RETURN_CAUGHT_EXCEPTION() return CaughtExceptionHResult(LOCATION_INFO); + +#define _CHECK_FAILED(expr) __pragma(warning(push)) \ + __pragma(warning(disable:4127)) /*disable condition is const warning*/ \ + FAILED(expr) \ + __pragma(warning(pop)) + +#define _HR_RET(hr) __pragma(warning(push)) \ + __pragma(warning(disable:26498)) /*disable constexpr warning */ \ + const HRESULT __hrRet = hr; \ + __pragma(warning(pop)) + +#define RETURN_HR(hr) do { _HR_RET(hr); if (_CHECK_FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); } return __hrRet; } while (0, 0) +#define RETURN_LAST_ERROR() do { return LogLastError(LOCATION_INFO); } while (0, 0) +#define RETURN_IF_FAILED(hr) do { _HR_RET(hr); if (FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); return __hrRet; }} while (0, 0) +#define RETURN_LAST_ERROR_IF(condition) do { if (condition) { return LogLastError(LOCATION_INFO); }} while (0, 0) +#define RETURN_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { return LogLastError(LOCATION_INFO); }} while (0, 0) + +#define _GOTO_FINISHED() __pragma(warning(push)) \ + __pragma(warning(disable:26438)) /*disable avoid goto warning*/ \ + goto Finished \ + __pragma(warning(pop)) + + +#define FINISHED(hrr) do { _HR_RET(hrr); if (_CHECK_FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); } hr = __hrRet; _GOTO_FINISHED(); } while (0, 0) +#define FINISHED_IF_FAILED(hrr) do { _HR_RET(hrr); if (FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); hr = __hrRet; _GOTO_FINISHED(); }} while (0, 0) +#define FINISHED_IF_NULL_ALLOC(ptr) do { if ((ptr) == nullptr) { hr = LogHResultFailed(LOCATION_INFO, E_OUTOFMEMORY); _GOTO_FINISHED(); }} while (0, 0) +#define FINISHED_LAST_ERROR_IF(condition) do { if (condition) { hr = LogLastError(LOCATION_INFO); _GOTO_FINISHED(); }} while (0, 0) +#define FINISHED_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { hr = LogLastError(LOCATION_INFO); _GOTO_FINISHED(); }} while (0, 0) + +#define THROW_HR(hr) do { _HR_RET(hr); ThrowResultException(LOCATION_INFO, LogHResultFailed(LOCATION_INFO, __hrRet)); } while (0, 0) +#define THROW_LAST_ERROR() do { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); } while (0, 0) +#define THROW_IF_FAILED(hr) do { _HR_RET(hr); if (FAILED(__hrRet)) { ThrowResultException(LOCATION_INFO, __hrRet); }} while (0, 0) +#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0) +#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LOCATION_INFO, LogLastError(LOCATION_INFO)); }} while (0, 0) + +#define THROW_IF_NULL_ALLOC(ptr) Throw_IfNullAlloc(ptr) + +#define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } +#define LOG_IF_FAILED(hr) LogHResultFailed(LOCATION_INFO, hr) +#define LOG_LAST_ERROR() LogLastErrorIf(LOCATION_INFO, true) +#define LOG_LAST_ERROR_IF(condition) LogLastErrorIf(LOCATION_INFO, condition) +#define SUCCEEDED_LOG(hr) SUCCEEDED(LOG_IF_FAILED(hr)) +#define FAILED_LOG(hr) FAILED(LOG_IF_FAILED(hr)) + +inline thread_local IHttpTraceContext* g_traceContext; + + __declspec(noinline) inline VOID TraceHRESULT(LOCATION_ARGUMENTS HRESULT hr) +{ + ::RaiseEvent(g_traceContext, nullptr, fileName, lineNumber, hr); +} + + __declspec(noinline) inline VOID TraceException(LOCATION_ARGUMENTS const std::exception& exception) +{ + ::RaiseEvent(g_traceContext, nullptr, fileName, lineNumber, exception.what()); +} + +class ResultException: public std::runtime_error +{ +public: + ResultException(HRESULT hr, LOCATION_ARGUMENTS_ONLY) : + runtime_error(format("HRESULT 0x%x returned at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY)), + m_hr(hr) + { + } + + HRESULT GetResult() const noexcept { return m_hr; } + +private: + +#pragma warning( push ) +#pragma warning ( disable : 26495 ) // bug in CA: m_hr is reported as uninitialized + const HRESULT m_hr = S_OK; +}; +#pragma warning( pop ) + + __declspec(noinline) inline VOID ReportUntypedException(LOCATION_ARGUMENTS_ONLY) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, LOCATION_FORMAT "Unhandled non-standard exception", LOCATION_CALL_ONLY); +} + + __declspec(noinline) inline HRESULT LogLastError(LOCATION_ARGUMENTS_ONLY) +{ + const auto lastError = GetLastError(); + const auto hr = HRESULT_FROM_WIN32(lastError); + + TraceHRESULT(LOCATION_CALL hr); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, LOCATION_FORMAT "Operation failed with LastError: %d HR: 0x%x", LOCATION_CALL lastError, hr); + + return hr; +} + + __declspec(noinline) inline bool LogLastErrorIf(LOCATION_ARGUMENTS_ONLY, bool condition) +{ + if (condition) + { + LogLastError(LOCATION_CALL_ONLY); + } + + return condition; +} + + __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS const std::exception& exception) +{ + TraceException(LOCATION_CALL exception); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Exception '%s' caught at " LOCATION_FORMAT, exception.what(), LOCATION_CALL_ONLY); +} + + __declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS HRESULT hr) +{ + if (FAILED(hr)) + { + TraceHRESULT(LOCATION_CALL hr); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Failed HRESULT returned: 0x%x at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY); + } + return hr; +} + +__declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ONLY) +{ + try + { + throw; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + catch (const ResultException& exception) + { + ReportException(LOCATION_CALL exception); + return exception.GetResult(); + } + catch (const std::exception& exception) + { + ReportException(LOCATION_CALL exception); + return HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION); + } + catch (...) + { + ReportUntypedException(LOCATION_CALL_ONLY); + return HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION); + } +} + +[[noreturn]] + __declspec(noinline) inline void ThrowResultException(LOCATION_ARGUMENTS HRESULT hr) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Throwing ResultException for HRESULT 0x%x at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY); + throw ResultException(hr, LOCATION_CALL_ONLY); +} + +template auto Throw_IfNullAlloc(PointerT pointer) +{ + if (pointer == nullptr) + { + throw std::bad_alloc(); + } + return pointer; +} +__declspec(noinline) inline std::wstring GetUnexpectedExceptionMessage(const std::runtime_error& ex) +{ + return format(L"Unexpected exception: %S", ex.what()); +} + +class TraceContextScope: NonCopyable +{ +public: + TraceContextScope(IHttpTraceContext* pTraceContext) noexcept + { + m_pPreviousTraceContext = g_traceContext; + g_traceContext = pTraceContext; + } + + ~TraceContextScope() + { + g_traceContext = m_pPreviousTraceContext; + } + private: + IHttpTraceContext* m_pPreviousTraceContext; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.cpp new file mode 100644 index 0000000000..2831eac64d --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.cpp @@ -0,0 +1,169 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "file_utility.h" + +#include +#include "debugutil.h" +#include "exceptions.h" + +HRESULT +FILE_UTILITY::IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc +) +{ + HRESULT hr = S_OK; + STRU strTempPath; + + if ( pszPath == NULL || pfIsUnc == NULL ) + { + hr = E_INVALIDARG; + goto Finished; + } + + hr = MakePathCanonicalizationProof( (LPWSTR) pszPath, &strTempPath ); + if ( FAILED(hr) ) + { + goto Finished; + } + + // + // MakePathCanonicalizationProof will map \\?\UNC, \\.\UNC and \\ to \\?\UNC + // + (*pfIsUnc) = ( _wcsnicmp( strTempPath.QueryStr(), L"\\\\?\\UNC\\", 8 /* sizeof \\?\UNC\ */) == 0 ); + +Finished: + + return hr; +} + +HRESULT +FILE_UTILITY::ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStruFullPath +) +{ + HRESULT hr = S_OK; + STRU strFileFullPath; + LPWSTR pszFullPath = NULL; + + // if relative path, prefix with root path and then convert to absolute path. + if ( PathIsRelative(pszPath) ) + { + hr = strFileFullPath.Copy(pszRootPath); + if(FAILED(hr)) + { + goto Finished; + } + + if(!strFileFullPath.EndsWith(L"\\")) + { + hr = strFileFullPath.Append(L"\\"); + if(FAILED(hr)) + { + goto Finished; + } + } + } + + hr = strFileFullPath.Append( pszPath ); + if (FAILED(hr)) + { + goto Finished; + } + + pszFullPath = new WCHAR[ strFileFullPath.QueryCCH() + 1]; + + if(_wfullpath( pszFullPath, + strFileFullPath.QueryStr(), + strFileFullPath.QueryCCH() + 1 ) == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + // convert to canonical path + hr = MakePathCanonicalizationProof( pszFullPath, pStruFullPath ); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + if ( pszFullPath != NULL ) + { + delete[] pszFullPath; + pszFullPath = NULL; + } + + return hr; +} + +HRESULT +FILE_UTILITY::EnsureDirectoryPathExist( + _In_ LPCWSTR pszPath +) +{ + HRESULT hr = S_OK; + STRU struPath; + DWORD dwPosition = 0; + BOOL fDone = FALSE; + BOOL fUnc = FALSE; + + struPath.Copy(pszPath); + hr = IsPathUnc(pszPath, &fUnc); + if (FAILED(hr)) + { + goto Finished; + } + if (fUnc) + { + // "\\?\UNC\" + dwPosition = 8; + } + else if (struPath.IndexOf(L'?', 0) != -1) + { + // sceanrio "\\?\" + dwPosition = 4; + } + while (!fDone) + { + dwPosition = struPath.IndexOf(L'\\', dwPosition + 1); + if (dwPosition == -1) + { + // not found '/' + fDone = TRUE; + goto Finished; + } + else if (dwPosition ==0) + { + hr = ERROR_INTERNAL_ERROR; + goto Finished; + } + else if (struPath.QueryStr()[dwPosition-1] == L':') + { + // skip volume case + continue; + } + else + { + struPath.QueryStr()[dwPosition] = L'\0'; + } + + if (!CreateDirectory(struPath.QueryStr(), NULL) && + ERROR_ALREADY_EXISTS != GetLastError()) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + fDone = TRUE; + goto Finished; + } + struPath.QueryStr()[dwPosition] = L'\\'; + } + +Finished: + return hr; +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.h new file mode 100644 index 0000000000..686e810cbe --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/file_utility.h @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "stdafx.h" + +#include +#include "stringu.h" + +class FILE_UTILITY +{ +public: + + static + HRESULT + ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStrFullPath + ); + + static + HRESULT + EnsureDirectoryPathExist( + _In_ LPCWSTR pszPath + ); + + static + std::string + GetHtml(HMODULE module, int page); + +private: + static + HRESULT + IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc + ); + +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cpp similarity index 98% rename from src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cpp index 7aeb0999c0..63183adf01 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.cpp @@ -1,7 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#include "stdafx.h" +#include "fx_ver.h" + +#include +#include +#include fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build) : m_major(major) @@ -189,4 +193,4 @@ bool fx_ver_t::parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_ bool valid = parse_internal(ver, fx_ver, parse_only_production); assert(!valid || fx_ver->as_str() == ver); return valid; -} \ No newline at end of file +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.h index 1626b2697c..723a0da360 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/fx_ver.h @@ -3,6 +3,8 @@ #pragma once +#include + // Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not // compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 struct fx_ver_t @@ -11,19 +13,17 @@ struct fx_ver_t fx_ver_t(int major, int minor, int patch, const std::wstring& pre); fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); - int get_major() const { return m_major; } - int get_minor() const { return m_minor; } - int get_patch() const { return m_patch; } + int get_major() const noexcept { return m_major; } + int get_minor() const noexcept { return m_minor; } + int get_patch() const noexcept { return m_patch; } - void set_major(int m) { m_major = m; } - void set_minor(int m) { m_minor = m; } - void set_patch(int p) { m_patch = p; } + void set_major(int m) noexcept { m_major = m; } + void set_minor(int m) noexcept { m_minor = m; } + void set_patch(int p) noexcept { m_patch = p; } - bool is_prerelease() const { return !m_pre.empty(); } + bool is_prerelease() const noexcept { return !m_pre.empty(); } std::wstring as_str() const; - std::wstring prerelease_glob() const; - std::wstring patch_glob() const; bool operator ==(const fx_ver_t& b) const; bool operator !=(const fx_ver_t& b) const; @@ -43,4 +43,3 @@ private: static int compare(const fx_ver_t&a, const fx_ver_t& b); }; - diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index 1ad02feba2..bc27526a41 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -1,237 +1,129 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "stdafx.h" +#include "hostfxr_utility.h" -HOSTFXR_UTILITY::HOSTFXR_UTILITY() -{ -} +#include +#include "fx_ver.h" +#include "debugutil.h" +#include "exceptions.h" +#include "HandleWrapper.h" +#include "Environment.h" +#include "StringHelpers.h" -HOSTFXR_UTILITY::~HOSTFXR_UTILITY() -{ -} +namespace fs = std::filesystem; -// -// Runs a standalone appliction. -// The folder structure looks like this: -// Application/ -// hostfxr.dll -// Application.exe -// Application.dll -// etc. -// We get the full path to hostfxr.dll and Application.dll and run hostfxr_main, -// passing in Application.dll. -// Assuming we don't need Application.exe as the dll is the actual application. -// -HRESULT -HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( - PCWSTR pwzExeAbsolutePath, // includes .exe file extension. - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - HANDLE hEventLog, - _Inout_ STRU* struHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ PWSTR** ppwzArgv -) -{ - HRESULT hr = S_OK; - STRU struDllPath; - STRU struArguments; - STRU struHostFxrPath; - STRU struRuntimeConfigLocation; - DWORD dwPosition; - - // Obtain the app name from the processPath section. - if ( FAILED( hr = struDllPath.Copy( pwzExeAbsolutePath ) ) ) - { - goto Finished; - } - - dwPosition = struDllPath.LastIndexOf( L'.', 0 ); - if ( dwPosition == -1 ) - { - hr = E_FAIL; - goto Finished; - } - - hr = UTILITY::ConvertPathToFullPath( L".\\hostfxr.dll", pcwzApplicationPhysicalPath, &struHostFxrPath ); - if ( FAILED( hr ) ) - { - goto Finished; - } - - struDllPath.QueryStr()[dwPosition] = L'\0'; - if (FAILED(hr = struDllPath.SyncWithBuffer())) - { - goto Finished; - } - - if ( !UTILITY::CheckIfFileExists( struHostFxrPath.QueryStr() ) ) - { - // Most likely a full framework app. - // Check that the runtime config file doesn't exist in the folder as another heuristic. - if (FAILED(hr = struRuntimeConfigLocation.Copy(struDllPath)) || - FAILED(hr = struRuntimeConfigLocation.Append( L".runtimeconfig.json" ))) - { - goto Finished; - } - if (!UTILITY::CheckIfFileExists(struRuntimeConfigLocation.QueryStr())) - { - - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP, - ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG, - pcwzApplicationPhysicalPath, - hr); - } - else - { - // If a runtime config file does exist, report a file not found on the app.exe - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND, - ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG, - pcwzApplicationPhysicalPath, - hr); - } - - goto Finished; - } - - if (FAILED(hr = struHostFxrDllLocation->Copy(struHostFxrPath))) - { - goto Finished; - } - - - if (FAILED(hr = struDllPath.Append(L".dll"))) - { - goto Finished; - } - - if (!UTILITY::CheckIfFileExists(struDllPath.QueryStr())) - { - // Treat access issue as File not found - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - goto Finished; - } - - if (FAILED(hr = struArguments.Copy(struDllPath)) || - FAILED(hr = struArguments.Append(L" ")) || - FAILED(hr = struArguments.Append(pcwzArguments))) - { - goto Finished; - } - - if (FAILED(hr = ParseHostfxrArguments( - struArguments.QueryStr(), - pwzExeAbsolutePath, - pcwzApplicationPhysicalPath, - hEventLog, - pdwArgCount, - ppwzArgv))) - { - goto Finished; - } - -Finished: - - return hr; -} - -HRESULT +void HOSTFXR_UTILITY::GetHostFxrParameters( - HANDLE hEventLog, - PCWSTR pcwzProcessPath, - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - _Inout_ STRU* struHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** pbstrArgv + const fs::path &processPath, + const fs::path &applicationPhysicalPath, + const std::wstring &applicationArguments, + fs::path &hostFxrDllPath, + fs::path &dotnetExePath, + std::vector &arguments ) { - HRESULT hr = S_OK; - STRU struSystemPathVariable; - STRU struAbsolutePathToHostFxr; - STRU struAbsolutePathToDotnet; - STRU struEventMsg; - STACK_STRU(struExpandedProcessPath, MAX_PATH); - STACK_STRU(struExpandedArguments, MAX_PATH); + LOG_INFOF(L"Resolving hostfxr parameters for application: '%ls' arguments: '%ls' path: '%ls'", + processPath.c_str(), + applicationArguments.c_str(), + applicationPhysicalPath.c_str()); + arguments = std::vector(); - // Copy and Expand the processPath and Arguments. - if (FAILED(hr = struExpandedProcessPath.CopyAndExpandEnvironmentStrings(pcwzProcessPath)) - || FAILED(hr = struExpandedArguments.CopyAndExpandEnvironmentStrings(pcwzArguments))) + fs::path expandedProcessPath = Environment::ExpandEnvironmentVariables(processPath); + const auto expandedApplicationArguments = Environment::ExpandEnvironmentVariables(applicationArguments); + + LOG_INFOF(L"Known dotnet.exe location: '%ls'", dotnetExePath.c_str()); + + if (!expandedProcessPath.has_extension()) { - goto Finished; + // The only executable extension inprocess supports + expandedProcessPath.replace_extension(".exe"); } - - // Convert the process path an absolute path to our current application directory. - // If the path is already an absolute path, it will be unchanged. - hr = UTILITY::ConvertPathToFullPath( - struExpandedProcessPath.QueryStr(), - pcwzApplicationPhysicalPath, - &struAbsolutePathToDotnet - ); - - if (FAILED(hr)) + else if (!ends_with(expandedProcessPath, L".exe", true)) { - goto Finished; + throw InvalidOperationException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str())); } // Check if the absolute path is to dotnet or not. - if (struAbsolutePathToDotnet.EndsWith(L"dotnet.exe") || struAbsolutePathToDotnet.EndsWith(L"dotnet")) + if (IsDotnetExecutable(expandedProcessPath)) { - // - // The processPath ends with dotnet.exe or dotnet - // like: C:\Program Files\dotnet\dotnet.exe, C:\Program Files\dotnet\dotnet, dotnet.exe, or dotnet. - // Get the absolute path to dotnet. If the path is already an absolute path, it will return that path - // - if (FAILED(hr = HOSTFXR_UTILITY::GetAbsolutePathToDotnet(&struAbsolutePathToDotnet))) // Make sure to append the dotnet.exe path correctly here (pass in regular path)? + LOG_INFOF(L"Process path '%ls' is dotnet, treating application as portable", expandedProcessPath.c_str()); + + if (applicationArguments.empty()) { - goto Finished; + throw InvalidOperationException(L"Application arguments are empty."); } - if (FAILED(hr = GetAbsolutePathToHostFxr(&struAbsolutePathToDotnet, hEventLog, &struAbsolutePathToHostFxr))) + if (dotnetExePath.empty()) { - goto Finished; + dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, expandedProcessPath); } - if (FAILED(hr = ParseHostfxrArguments( - struExpandedArguments.QueryStr(), - struAbsolutePathToDotnet.QueryStr(), - pcwzApplicationPhysicalPath, - hEventLog, - pdwArgCount, - pbstrArgv))) - { - goto Finished; - } + hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); - if (FAILED(hr = struHostFxrDllLocation->Copy(struAbsolutePathToHostFxr))) - { - goto Finished; - } + arguments.push_back(dotnetExePath); + AppendArguments( + expandedApplicationArguments, + applicationPhysicalPath, + arguments, + true); } else { + LOG_INFOF(L"Process path '%ls' is not dotnet, treating application as standalone or portable with bootstrapper", expandedProcessPath.c_str()); + + auto executablePath = expandedProcessPath; + + if (executablePath.is_relative()) + { + executablePath = applicationPhysicalPath / expandedProcessPath; + } + // // The processPath is a path to the application executable // like: C:\test\MyApp.Exe or MyApp.Exe // Check if the file exists, and if it does, get the parameters for a standalone application // - if (UTILITY::CheckIfFileExists(struAbsolutePathToDotnet.QueryStr())) + if (is_regular_file(executablePath)) { - hr = GetStandaloneHostfxrParameters( - struAbsolutePathToDotnet.QueryStr(), - pcwzApplicationPhysicalPath, - struExpandedArguments.QueryStr(), - hEventLog, - struHostFxrDllLocation, - pdwArgCount, - pbstrArgv); + auto applicationDllPath = executablePath; + applicationDllPath.replace_extension(".dll"); + + LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str()); + if (!is_regular_file(applicationDllPath)) + { + throw InvalidOperationException(format(L"Application .dll was not found at %s", applicationDllPath.c_str())); + } + + hostFxrDllPath = executablePath.parent_path() / "hostfxr.dll"; + LOG_INFOF(L"Checking hostfxr.dll at '%ls'", hostFxrDllPath.c_str()); + if (is_regular_file(hostFxrDllPath)) + { + LOG_INFOF(L"hostfxr.dll found app local at '%ls', treating application as standalone", hostFxrDllPath.c_str()); + // For standalone apps we need .exe to be argv[0], dll would be discovered next to it + arguments.push_back(executablePath); + } + else + { + LOG_INFOF(L"hostfxr.dll found app local at '%ls', treating application as portable with launcher", hostFxrDllPath.c_str()); + + // passing "dotnet" here because we don't know where dotnet.exe should come from + // so trying all fallbacks is appropriate + if (dotnetExePath.empty()) + { + dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, L"dotnet"); + } + hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); + + // For portable with launcher apps we need dotnet.exe to be argv[0] and .dll be argv[1] + arguments.push_back(dotnetExePath); + arguments.push_back(applicationDllPath); + } + + AppendArguments( + expandedApplicationArguments, + applicationPhysicalPath, + arguments); } else { @@ -239,310 +131,189 @@ HOSTFXR_UTILITY::GetHostFxrParameters( // If the processPath file does not exist and it doesn't include dotnet.exe or dotnet // then it is an invalid argument. // - hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_GENERAL_ERROR_MSG, - ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, - struExpandedProcessPath.QueryStr(), - hr); + throw InvalidOperationException(format(L"Executable was not found at '%s'", executablePath.c_str())); } } - -Finished: - - return hr; } -// -// Forms the argument list in HOSTFXR_PARAMETERS. -// Sets the ArgCount and Arguments. -// Arg structure: -// argv[0] = Path to exe activating hostfxr. -// argv[1] = L"exec" -// argv[2] = absolute path to dll. -// -HRESULT -HOSTFXR_UTILITY::ParseHostfxrArguments( - PCWSTR pwzArgumentsFromConfig, - PCWSTR pwzExePath, - PCWSTR pcwzApplicationPhysicalPath, - HANDLE hEventLog, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** pbstrArgv +BOOL +HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath) +{ + return ends_with(dotnetPath, L"dotnet.exe", true); +} + +void +HOSTFXR_UTILITY::AppendArguments( + const std::wstring &applicationArguments, + const fs::path &applicationPhysicalPath, + std::vector &arguments, + bool expandDllPaths ) { - UNREFERENCED_PARAMETER( hEventLog ); // TODO use event log to set errors. - - DBG_ASSERT(dwArgCount != NULL); - DBG_ASSERT(pwzArgv != NULL); - - HRESULT hr = S_OK; - INT argc = 0; - BSTR* argv = NULL; - LPWSTR* pwzArgs = NULL; - STRU struTempPath; - INT intArgsProcessed = 0; - - // If we call CommandLineToArgvW with an empty string, argc is 5 for some interesting reason. - // Protectively guard against this by check if the string is null or empty. - if (pwzArgumentsFromConfig == NULL || wcscmp(pwzArgumentsFromConfig, L"") == 0) + if (applicationArguments.empty()) { - hr = E_INVALIDARG; - goto Finished; + return; } - pwzArgs = CommandLineToArgvW(pwzArgumentsFromConfig, &argc); + // don't throw while trying to expand arguments + std::error_code ec; - if (pwzArgs == NULL) + // Try to treat entire arguments section as a single path + if (expandDllPaths) { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Failure; - } - - argv = new BSTR[argc + 1]; - if (argv == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - - argv[0] = SysAllocString(pwzExePath); - - if (argv[0] == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - - // Try to convert the application dll from a relative to an absolute path - // Don't record this failure as pwzArgs[0] may already be an absolute path to the dll. - for (intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++) - { - struTempPath.Copy(pwzArgs[intArgsProcessed]); - if (struTempPath.EndsWith(L".dll")) + fs::path argumentAsPath = applicationArguments; + if (is_regular_file(argumentAsPath, ec)) { - if (SUCCEEDED(UTILITY::ConvertPathToFullPath(pwzArgs[intArgsProcessed], pcwzApplicationPhysicalPath, &struTempPath))) - { - argv[intArgsProcessed + 1] = SysAllocString(struTempPath.QueryStr()); - } - else - { - argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]); - } - if (argv[intArgsProcessed + 1] == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } + LOG_INFOF(L"Treating '%ls' as a single path argument", applicationArguments.c_str()); + arguments.push_back(argumentAsPath); + return; } - else + + if (argumentAsPath.is_relative()) { - argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]); - if (argv[intArgsProcessed + 1] == NULL) + argumentAsPath = applicationPhysicalPath / argumentAsPath; + if (is_regular_file(argumentAsPath, ec)) { - hr = E_OUTOFMEMORY; - goto Failure; + LOG_INFOF(L"Converted argument '%ls' to '%ls'", applicationArguments.c_str(), argumentAsPath.c_str()); + arguments.push_back(argumentAsPath); + return; } } } - *pbstrArgv = argv; - *pdwArgCount = argc + 1; - - goto Finished; - -Failure: - if (argv != NULL) + int argc = 0; + auto pwzArgs = std::unique_ptr(CommandLineToArgvW(applicationArguments.c_str(), &argc)); + if (!pwzArgs) { - // intArgsProcess - 1 here as if we fail to allocated the ith string - // we don't want to free it. - for (INT i = 0; i < intArgsProcessed - 1; i++) + throw InvalidOperationException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str())); + } + + for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++) + { + std::wstring argument = pwzArgs[intArgsProcessed]; + + // Try expanding arguments ending in .dll to a full paths + if (expandDllPaths && ends_with(argument, L".dll", true)) { - SysFreeString(argv[i]); + fs::path argumentAsPath = argument; + if (argumentAsPath.is_relative()) + { + argumentAsPath = applicationPhysicalPath / argumentAsPath; + if (is_regular_file(argumentAsPath, ec)) + { + LOG_INFOF(L"Converted argument '%ls' to '%ls'", argument.c_str(), argumentAsPath.c_str()); + argument = argumentAsPath; + } + } } - } - delete[] argv; - -Finished: - if (pwzArgs != NULL) - { - LocalFree(pwzArgs); - DBG_ASSERT(pwzArgs == NULL); + arguments.push_back(argument); } - return hr; } -HRESULT +// The processPath ends with dotnet.exe or dotnet +// like: C:\Program Files\dotnet\dotnet.exe, C:\Program Files\dotnet\dotnet, dotnet.exe, or dotnet. +// Get the absolute path to dotnet. If the path is already an absolute path, it will return that path +fs::path HOSTFXR_UTILITY::GetAbsolutePathToDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet + const fs::path & applicationPath, + const fs::path & requestedPath ) { - HRESULT hr = S_OK; + LOG_INFOF(L"Resolving absolute path to dotnet.exe from '%ls'", requestedPath.c_str()); + + auto processPath = requestedPath; + if (processPath.is_relative()) + { + processPath = applicationPath / processPath; + } // // If we are given an absolute path to dotnet.exe, we are done // - if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) + if (is_regular_file(processPath)) { - goto Finished; - } + LOG_INFOF(L"Found dotnet.exe at '%ls'", processPath.c_str()); - // - // If the path was C:\Program Files\dotnet\dotnet - // We need to try appending .exe and check if the file exists too. - // - if (FAILED(hr = pStruAbsolutePathToDotnet->Append(L".exe"))) - { - goto Finished; - } - - if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) - { - goto Finished; + return processPath; } // At this point, we are calling where.exe to find dotnet. // If we encounter any failures, try getting dotnet.exe from the // backup location. - if (!InvokeWhereToFindDotnet(pStruAbsolutePathToDotnet)) + // Only do it if no path is specified + if (requestedPath.has_parent_path()) { - hr = GetAbsolutePathToDotnetFromProgramFiles(pStruAbsolutePathToDotnet); + LOG_INFOF(L"Absolute path to dotnet.exe was not found at '%ls'", requestedPath.c_str()); + + throw InvalidOperationException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str())); } -Finished: + const auto dotnetViaWhere = InvokeWhereToFindDotnet(); + if (dotnetViaWhere.has_value()) + { + LOG_INFOF(L"Found dotnet.exe via where.exe invocation at '%ls'", dotnetViaWhere.value().c_str()); - return hr; + return dotnetViaWhere.value(); + } + + const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles(); + if (programFilesLocation.has_value()) + { + LOG_INFOF(L"Found dotnet.exe in Program Files at '%ls'", programFilesLocation.value().c_str()); + + return programFilesLocation.value(); + } + + LOG_INFOF(L"dotnet.exe not found"); + throw InvalidOperationException(format( + L"Could not find dotnet.exe at '%s' or using the system PATH environment variable." + " Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process.", + processPath.c_str())); } -HRESULT +fs::path HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( - STRU* pStruAbsolutePathToDotnet, - HANDLE hEventLog, - STRU* pStruAbsolutePathToHostfxr + const fs::path & dotnetPath ) { - HRESULT hr = S_OK; - STRU struHostFxrPath; - STRU struHostFxrSearchExpression; - STRU struHighestDotnetVersion; - STRU struEventMsg; - std::vector vVersionFolders; - DWORD dwPosition = 0; + std::vector versionFolders; + const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr"; - if (FAILED(hr = struHostFxrPath.Copy(pStruAbsolutePathToDotnet))) + LOG_INFOF(L"Resolving absolute path to hostfxr.dll from '%ls'", dotnetPath.c_str()); + + if (!is_directory(hostFxrBase)) { - goto Finished; + throw InvalidOperationException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str())); } - dwPosition = struHostFxrPath.LastIndexOf(L'\\', 0); - if (dwPosition == -1) + FindDotNetFolders(hostFxrBase, versionFolders); + + if (versionFolders.empty()) { - hr = E_FAIL; - goto Finished; + throw InvalidOperationException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str())); } - struHostFxrPath.QueryStr()[dwPosition] = L'\0'; + const auto highestVersion = FindHighestDotNetVersion(versionFolders); + const auto hostFxrPath = hostFxrBase / highestVersion / "hostfxr.dll"; - if (FAILED(hr = struHostFxrPath.SyncWithBuffer()) || - FAILED(hr = struHostFxrPath.Append(L"\\"))) + if (!is_regular_file(hostFxrPath)) { - goto Finished; + throw InvalidOperationException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str())); } - hr = struHostFxrPath.Append(L"host\\fxr"); - if (FAILED(hr)) - { - goto Finished; - } - - if (!UTILITY::DirectoryExists(&struHostFxrPath)) - { - hr = ERROR_BAD_ENVIRONMENT; - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - struEventMsg.QueryStr(), - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - // Find all folders under host\\fxr\\ for version numbers. - hr = struHostFxrSearchExpression.Copy(struHostFxrPath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = struHostFxrSearchExpression.Append(L"\\*"); - if (FAILED(hr)) - { - goto Finished; - } - - // As we use the logic from core-setup, we are opting to use std here. - UTILITY::FindDotNetFolders(struHostFxrSearchExpression.QueryStr(), &vVersionFolders); - - if (vVersionFolders.size() == 0) - { - hr = HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - hr = UTILITY::FindHighestDotNetVersion(vVersionFolders, &struHighestDotnetVersion); - if (FAILED(hr)) - { - goto Finished; - } - - if (FAILED(hr = struHostFxrPath.Append(L"\\")) - || FAILED(hr = struHostFxrPath.Append(struHighestDotnetVersion.QueryStr())) - || FAILED(hr = struHostFxrPath.Append(L"\\hostfxr.dll"))) - { - goto Finished; - } - - if (!UTILITY::CheckIfFileExists(struHostFxrPath.QueryStr())) - { - // ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG - hr = HRESULT_FROM_WIN32(ERROR_FILE_INVALID); - UTILITY::LogEventF(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr); - goto Finished; - } - - if (FAILED(hr = pStruAbsolutePathToHostfxr->Copy(struHostFxrPath))) - { - goto Finished; - } - -Finished: - return hr; + LOG_INFOF(L"hostfxr.dll located at '%ls'", hostFxrPath.c_str()); + return hostFxrPath; } // // Tries to call where.exe to find the location of dotnet.exe. // Will check that the bitness of dotnet matches the current // worker process bitness. -// Returns true if a valid dotnet was found, else false. +// Returns true if a valid dotnet was found, else false.R // -BOOL -HOSTFXR_UTILITY::InvokeWhereToFindDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet -) +std::optional +HOSTFXR_UTILITY::InvokeWhereToFindDotnet() { HRESULT hr = S_OK; // Arguments to call where.exe @@ -551,9 +322,11 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( SECURITY_ATTRIBUTES securityAttributes; CHAR pzFileContents[READ_BUFFER_SIZE]; - HANDLE hStdOutReadPipe = INVALID_HANDLE_VALUE; - HANDLE hStdOutWritePipe = INVALID_HANDLE_VALUE; - LPWSTR pwzDotnetName = NULL; + HandleWrapper hStdOutReadPipe; + HandleWrapper hStdOutWritePipe; + HandleWrapper hProcess; + HandleWrapper hThread; + CComBSTR pwzDotnetName = NULL; DWORD dwFilePointer; BOOL fIsWow64Process; BOOL fIsCurrentProcess64Bit; @@ -564,29 +337,18 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( DWORD dwBinaryType; INT index = 0; INT prevIndex = 0; - BOOL fProcessCreationResult = FALSE; - BOOL fResult = FALSE; + std::optional result; // Set the security attributes for the read/write pipe securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.bInheritHandle = TRUE; - // Reset the path to dotnet as we will be using whether the string is - // empty or not as state - pStruAbsolutePathToDotnet->Reset(); + LOG_INFO(L"Invoking where.exe to find dotnet.exe"); // Create a read/write pipe that will be used for reading the result of where.exe - if (!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - if (!SetHandleInformation(hStdOutReadPipe, HANDLE_FLAG_INHERIT, 0)) - { - hr = ERROR_FILE_INVALID; - goto Finished; - } + FINISHED_LAST_ERROR_IF(!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)); + FINISHED_LAST_ERROR_IF(!SetHandleInformation(hStdOutReadPipe, HANDLE_FLAG_INHERIT, 0)); // Set the stdout and err pipe to the write pipes. startupInfo.cb = sizeof(startupInfo); @@ -596,14 +358,10 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( // CreateProcess requires a mutable string to be passed to commandline // See https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083/ - pwzDotnetName = SysAllocString(L"\"where.exe\" dotnet.exe"); - if (pwzDotnetName == NULL) - { - goto Finished; - } + pwzDotnetName = L"\"where.exe\" dotnet.exe"; // Create a process to invoke where.exe - fProcessCreationResult = CreateProcessW(NULL, + FINISHED_LAST_ERROR_IF(!CreateProcessW(NULL, pwzDotnetName, NULL, NULL, @@ -613,31 +371,21 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( NULL, &startupInfo, &processInformation - ); + )); - if (!fProcessCreationResult) - { - goto Finished; - } + // Store handles into wrapper so they get closed automatically + hProcess = processInformation.hProcess; + hThread = processInformation.hThread; - // Wait for where.exe to return, waiting 2 seconds. - if (WaitForSingleObject(processInformation.hProcess, 2000) != WAIT_OBJECT_0) - { - // Timeout occured, terminate the where.exe process and return. - TerminateProcess(processInformation.hProcess, 2); - hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); - goto Finished; - } + // Wait for where.exe to return + WaitForSingleObject(processInformation.hProcess, INFINITE); // // where.exe will return 0 on success, 1 if the file is not found // and 2 if there was an error. Check if the exit code is 1 and set // a new hr result saying it couldn't find dotnet.exe // - if (!GetExitCodeProcess(processInformation.hProcess, &dwExitCode)) - { - goto Finished; - } + FINISHED_LAST_ERROR_IF (!GetExitCodeProcess(processInformation.hProcess, &dwExitCode)); // // In this block, if anything fails, we will goto our fallback of @@ -645,7 +393,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( // if (dwExitCode != 0) { - goto Finished; + FINISHED_IF_FAILED(E_FAIL); } // Where succeeded. @@ -653,38 +401,30 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( dwFilePointer = SetFilePointer(hStdOutReadPipe, 0, NULL, FILE_BEGIN); if (dwFilePointer == INVALID_SET_FILE_POINTER) { - goto Finished; + FINISHED_IF_FAILED(E_FAIL); } // // As the call to where.exe succeeded (dotnet.exe was found), ReadFile should not hang. // TODO consider putting ReadFile in a separate thread with a timeout to guarantee it doesn't block. // - if (!ReadFile(hStdOutReadPipe, pzFileContents, READ_BUFFER_SIZE, &dwNumBytesRead, NULL)) - { - goto Finished; - } + FINISHED_LAST_ERROR_IF (!ReadFile(hStdOutReadPipe, pzFileContents, READ_BUFFER_SIZE, &dwNumBytesRead, NULL)); if (dwNumBytesRead >= READ_BUFFER_SIZE) { // This shouldn't ever be this large. We could continue to call ReadFile in a loop, // however if someone had this many dotnet.exes on their machine. - goto Finished; + FINISHED_IF_FAILED(E_FAIL); } - hr = HRESULT_FROM_WIN32(GetLastError()); - if (FAILED(hr = struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead))) - { - goto Finished; - } + FINISHED_IF_FAILED(struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead)); + + LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr()); // Check the bitness of the currently running process // matches the dotnet.exe found. - if (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)) - { - // Calling IsWow64Process failed - goto Finished; - } + FINISHED_LAST_ERROR_IF (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)); + if (fIsWow64Process) { // 32 bit mode @@ -698,6 +438,8 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; } + LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit); + while (TRUE) { index = struDotnetLocationsString.IndexOf(L"\r\n", prevIndex); @@ -705,103 +447,73 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet( { break; } - if (FAILED(hr = struDotnetSubstring.Copy(&struDotnetLocationsString.QueryStr()[prevIndex], index - prevIndex))) - { - goto Finished; - } + + FINISHED_IF_FAILED(struDotnetSubstring.Copy(&struDotnetLocationsString.QueryStr()[prevIndex], index - prevIndex)); // \r\n is two wchars, so add 2 here. prevIndex = index + 2; - if (GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType) && - fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) + LOG_INFOF(L"Processing entry '%ls'", struDotnetSubstring.QueryStr()); + + if (LOG_LAST_ERROR_IF(!GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType))) + { + continue; + } + + LOG_INFOF(L"Binary type %d", dwBinaryType); + + if (fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) { // The bitness of dotnet matched with the current worker process bitness. - if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) - { - goto Finished; - } - fResult = TRUE; - break; + return std::make_optional(struDotnetSubstring.QueryStr()); } } -Finished: - - if (hStdOutReadPipe != INVALID_HANDLE_VALUE) - { - CloseHandle(hStdOutReadPipe); - } - if (hStdOutWritePipe != INVALID_HANDLE_VALUE) - { - CloseHandle(hStdOutWritePipe); - } - if (processInformation.hProcess != INVALID_HANDLE_VALUE) - { - CloseHandle(processInformation.hProcess); - } - if (processInformation.hThread != INVALID_HANDLE_VALUE) - { - CloseHandle(processInformation.hThread); - } - if (pwzDotnetName != NULL) - { - SysFreeString(pwzDotnetName); - } - - return fResult; + Finished: + return result; } +std::optional +HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles() +{ + const auto programFilesDotnet = fs::path(Environment::ExpandEnvironmentVariables(L"%ProgramFiles%")) / "dotnet" / "dotnet.exe"; + return is_regular_file(programFilesDotnet) ? std::make_optional(programFilesDotnet) : std::nullopt; +} -HRESULT -HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles( - _Inout_ STRU* pStruAbsolutePathToDotnet +std::wstring +HOSTFXR_UTILITY::FindHighestDotNetVersion( + _In_ std::vector & vFolders ) { - HRESULT hr = S_OK; - BOOL fFound = FALSE; - DWORD dwNumBytesRead = 0; - DWORD dwPathSize = MAX_PATH; - STRU struDotnetSubstring; - - while (!fFound) + fx_ver_t max_ver(-1, -1, -1); + for (const auto& dir : vFolders) { - if (FAILED(hr = struDotnetSubstring.Resize(dwPathSize))) + fx_ver_t fx_ver(-1, -1, -1); + if (fx_ver_t::parse(dir, &fx_ver, false)) { - goto Finished; - } - - dwNumBytesRead = GetEnvironmentVariable(L"ProgramFiles", struDotnetSubstring.QueryStr(), dwPathSize); - if (dwNumBytesRead == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - else if (dwNumBytesRead >= dwPathSize) - { - // - // The path to ProgramFiles should never be this long, but resize and try again. - dwPathSize *= 2 + 30; // for dotnet substring - } - else - { - if (FAILED(hr = struDotnetSubstring.SyncWithBuffer()) || - FAILED(hr = struDotnetSubstring.Append(L"\\dotnet\\dotnet.exe"))) - { - goto Finished; - } - if (!UTILITY::CheckIfFileExists(struDotnetSubstring.QueryStr())) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) - { - goto Finished; - } - fFound = TRUE; + max_ver = max(max_ver, fx_ver); } } -Finished: - return hr; + return max_ver.as_str(); +} + +VOID +HOSTFXR_UTILITY::FindDotNetFolders( + const std::filesystem::path &path, + _Out_ std::vector &pvFolders +) +{ + WIN32_FIND_DATAW data = {}; + const auto searchPattern = std::wstring(path) + L"\\*"; + HandleWrapper handle = FindFirstFileExW(searchPattern.c_str(), FindExInfoStandard, &data, FindExSearchNameMatch, nullptr, 0); + if (handle == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(); + return; + } + + do + { + pvFolders.emplace_back(data.cFileName); + } while (FindNextFileW(handle, &data)); } diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index e7703c5bfa..c015fadc88 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -3,6 +3,12 @@ #pragma once +#include +#include +#include +#include +#include + typedef INT(*hostfxr_get_native_search_directories_fn) (CONST INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size); typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); @@ -11,68 +17,75 @@ typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); class HOSTFXR_UTILITY { public: - HOSTFXR_UTILITY(); - ~HOSTFXR_UTILITY(); - - static - HRESULT - GetHostFxrParameters( - HANDLE hEventLog, - PCWSTR pcwzProcessPath, - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - _Inout_ STRU* pStruHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv - ); static - HRESULT - GetStandaloneHostfxrParameters( - PCWSTR pwzExeAbsolutePath, // includes .exe file extension. - PCWSTR pcwzApplicationPhysicalPath, - PCWSTR pcwzArguments, - HANDLE hEventLog, - _Inout_ STRU* pStruHostFxrDllLocation, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv + void + GetHostFxrParameters( + const std::filesystem::path &processPath, + const std::filesystem::path &applicationPhysicalPath, + const std::wstring &applicationArguments, + std::filesystem::path &hostFxrDllPath, + std::filesystem::path &dotnetExePath, + std::vector &arguments ); static - HRESULT - ParseHostfxrArguments( - PCWSTR pwzArgumentsFromConfig, - PCWSTR pwzExePath, - PCWSTR pcwzApplicationPhysicalPath, - HANDLE hEventLog, - _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv + void + AppendArguments( + const std::wstring &arugments, + const std::filesystem::path &applicationPhysicalPath, + std::vector &arguments, + bool expandDllPaths = false ); static - HRESULT - GetAbsolutePathToDotnet( - STRU* pStruAbsolutePathToDotnet - ); - - static - HRESULT - GetAbsolutePathToHostFxr( - _In_ STRU* pStruAbsolutePathToDotnet, - _In_ HANDLE hEventLog, - _Out_ STRU* pStruAbsolutePathToHostfxr - ); + std::optional + GetAbsolutePathToDotnetFromProgramFiles(); +private: static BOOL - InvokeWhereToFindDotnet( - _Inout_ STRU* pStruAbsolutePathToDotnet + IsDotnetExecutable( + const std::filesystem::path & dotnetPath ); static - HRESULT - GetAbsolutePathToDotnetFromProgramFiles( - _Inout_ STRU* pStruAbsolutePathToDotnet + VOID + FindDotNetFolders( + const std::filesystem::path& path, + std::vector & pvFolders ); + + static + std::wstring + FindHighestDotNetVersion( + std::vector & vFolders + ); + + static + std::filesystem::path + GetAbsolutePathToHostFxr( + const std::filesystem::path & dotnetPath + ); + + + static + std::optional + InvokeWhereToFindDotnet(); + + static + std::filesystem::path + GetAbsolutePathToDotnet( + const std::filesystem::path & applicationPath, + const std::filesystem::path & requestedPath + ); + + struct LocalFreeDeleter + { + void operator ()(_In_ LPWSTR* ptr) const + { + LocalFree(ptr); + } + }; }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp new file mode 100644 index 0000000000..060224c60d --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "hostfxroptions.h" + +#include "hostfxr_utility.h" +#include "debugutil.h" +#include "exceptions.h" +#include "EventLog.h" + +HRESULT HOSTFXR_OPTIONS::Create( + _In_ const std::wstring& pcwzDotnetExePath, + _In_ const std::wstring& pcwzProcessPath, + _In_ const std::wstring& pcwzApplicationPhysicalPath, + _In_ const std::wstring& pcwzArguments, + _Out_ std::unique_ptr& ppWrapper) +{ + std::filesystem::path knownDotnetLocation; + + if (!pcwzDotnetExePath.empty()) + { + knownDotnetLocation = pcwzDotnetExePath; + } + + try + { + std::filesystem::path hostFxrDllPath; + std::vector arguments; + HOSTFXR_UTILITY::GetHostFxrParameters( + pcwzProcessPath, + pcwzApplicationPhysicalPath, + pcwzArguments, + hostFxrDllPath, + knownDotnetLocation, + arguments); + + LOG_INFOF(L"Parsed hostfxr options: dotnet location: '%ls' hostfxr path: '%ls' arguments:", knownDotnetLocation.c_str(), hostFxrDllPath.c_str()); + for (size_t i = 0; i < arguments.size(); i++) + { + LOG_INFOF(L"Argument[%d] = '%ls'", i, arguments[i].c_str()); + } + ppWrapper = std::make_unique(knownDotnetLocation, hostFxrDllPath, arguments); + } + catch (InvalidOperationException &ex) + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_START_ERROR, + ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG, + pcwzApplicationPhysicalPath.c_str(), + ex.as_wstring().c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + catch (std::runtime_error &ex) + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_START_ERROR, + ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG, + pcwzApplicationPhysicalPath.c_str(), + GetUnexpectedExceptionMessage(ex).c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + CATCH_RETURN(); + + return S_OK; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h new file mode 100644 index 0000000000..243cc53ae7 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.h @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include +#include +#include +#include + +class HOSTFXR_OPTIONS +{ +public: + HOSTFXR_OPTIONS( + std::filesystem::path dotnetExeLocation, + std::filesystem::path hostFxrLocation, + std::vector arguments + ) noexcept + : m_dotnetExeLocation(std::move(dotnetExeLocation)), + m_hostFxrLocation(std::move(hostFxrLocation)), + m_arguments(std::move(arguments)) + {} + + void + GetArguments(DWORD &hostfxrArgc, std::unique_ptr &hostfxrArgv) const + { + hostfxrArgc = static_cast(m_arguments.size()); + hostfxrArgv = std::make_unique(hostfxrArgc); + for (DWORD i = 0; i < hostfxrArgc; ++i) + { + hostfxrArgv[i] = m_arguments[i].c_str(); + } + } + + const std::filesystem::path& + GetHostFxrLocation() const noexcept + { + return m_hostFxrLocation; + } + + const std::filesystem::path& + GetDotnetExeLocation() const noexcept + { + return m_dotnetExeLocation; + } + + static + HRESULT Create( + _In_ const std::wstring& pcwzExeLocation, + _In_ const std::wstring& pcwzProcessPath, + _In_ const std::wstring& pcwzApplicationPhysicalPath, + _In_ const std::wstring& pcwzArguments, + _Out_ std::unique_ptr& ppWrapper); + +private: + const std::filesystem::path m_dotnetExeLocation; + const std::filesystem::path m_hostFxrLocation; + const std::vector m_arguments; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/iapplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/iapplication.h new file mode 100644 index 0000000000..52a1a7e2a9 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/iapplication.h @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "irequesthandler.h" + +struct APPLICATION_PARAMETER +{ + LPCSTR pzName; + void *pValue; +}; + +class IAPPLICATION +{ +public: + virtual + VOID + Stop(bool fServerInitiated) = 0; + + virtual + ~IAPPLICATION() = 0 { }; + + virtual + VOID + ReferenceApplication() = 0; + + virtual + VOID + DereferenceApplication() = 0; + + virtual + HRESULT + TryCreateHandler( + _In_ IHttpContext *pHttpContext, + _Outptr_ IREQUEST_HANDLER **pRequestHandler) = 0; +}; + +struct IAPPLICATION_DELETER +{ + void operator ()(IAPPLICATION* application) const + { + application->DereferenceApplication(); + } +}; + +template< class APPLICATION > +std::unique_ptr ReferenceApplication(APPLICATION* application) +{ + application->ReferenceApplication(); + return std::unique_ptr(application); +}; + +template< class APPLICATION, typename ...Params > +std::unique_ptr make_application(Params&&... params) +{ +#pragma warning( push ) +#pragma warning ( disable : 26409 ) // Disable "Avoid using new", using custom deleter here + return std::unique_ptr(new APPLICATION(std::forward(params)...)); +#pragma warning( pop ) +} + +template< class TYPE > +TYPE FindParameter(LPCSTR sRequiredParameter, APPLICATION_PARAMETER *pParameters, DWORD nParameters) +{ + for (DWORD i = 0; i < nParameters; i++) + { + if (_stricmp(pParameters[i].pzName, sRequiredParameter) == 0) + { + return reinterpret_cast(pParameters[i].pValue); + } + } + return nullptr; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h new file mode 100644 index 0000000000..c89411f8a0 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include + +// +// Pure abstract class +// +class IREQUEST_HANDLER +{ +public: + + virtual + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler() = 0; + + virtual + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ) = 0; + + virtual + VOID + NotifyDisconnect() noexcept(false) = 0; + + virtual + ~IREQUEST_HANDLER( + VOID + ) = 0 { } + + virtual + VOID + ReferenceRequestHandler( + VOID + ) = 0; + + virtual + VOID + DereferenceRequestHandler( + VOID + ) = 0; +}; + + +struct IREQUEST_HANDLER_DELETER +{ + void operator ()(IREQUEST_HANDLER* application) const + { + application->DereferenceRequestHandler(); + } +}; + +inline std::unique_ptr ReferenceRequestHandler(IREQUEST_HANDLER* handler) +{ + handler->ReferenceRequestHandler(); + return std::unique_ptr(handler); +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.cxx deleted file mode 100644 index bcf1887d35..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.cxx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "stdafx.h" - -REQUEST_HANDLER::REQUEST_HANDLER( - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication) - : m_cRefs(1) -{ - m_pW3Context = pW3Context; - m_pApplication = pApplication; - m_pModuleId = *pModuleId; -} - - -REQUEST_HANDLER::~REQUEST_HANDLER() -{ -} - -VOID -REQUEST_HANDLER::ReferenceRequestHandler( - VOID -) const -{ - InterlockedIncrement(&m_cRefs); -} - - -VOID -REQUEST_HANDLER::DereferenceRequestHandler( - VOID -) const -{ - DBG_ASSERT(m_cRefs != 0); - - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) - { - delete this; - } - -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.h index 28f4fb725e..8bac2885b0 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/requesthandler.h @@ -3,57 +3,77 @@ #pragma once -#include "stdafx.h" -#include "application.h" +#include "irequesthandler.h" +#include "ntassert.h" +#include "exceptions.h" // -// Abstract class +// Pure abstract class // -class REQUEST_HANDLER +class REQUEST_HANDLER: public virtual IREQUEST_HANDLER { public: - REQUEST_HANDLER( - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication - ); + REQUEST_HANDLER(IHttpContext& pHttpContext) noexcept : m_pHttpContext(pHttpContext) + { + } virtual REQUEST_NOTIFICATION_STATUS - OnExecuteRequestHandler() = 0; + ExecuteRequestHandler() = 0; + + VOID + ReferenceRequestHandler() noexcept override + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceRequestHandler() noexcept override + { + DBG_ASSERT(m_cRefs != 0); + + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler() final + { + TraceContextScope traceScope(m_pHttpContext.GetTraceContext()); + return ExecuteRequestHandler(); + } - virtual REQUEST_NOTIFICATION_STATUS OnAsyncCompletion( DWORD cbCompletion, HRESULT hrCompletionStatus - ) = 0; + ) final + { + TraceContextScope traceScope(m_pHttpContext.GetTraceContext()); + return AsyncCompletion(cbCompletion, hrCompletionStatus); + }; virtual - VOID - TerminateRequest( - bool fClientInitiated - ) = 0; + REQUEST_NOTIFICATION_STATUS AsyncCompletion(DWORD cbCompletion, HRESULT hrCompletionStatus) + { + UNREFERENCED_PARAMETER(cbCompletion); + UNREFERENCED_PARAMETER(hrCompletionStatus); + // We shouldn't get here in default implementation + DBG_ASSERT(FALSE); + return RQ_NOTIFICATION_FINISH_REQUEST; + } - virtual - ~REQUEST_HANDLER( - VOID - ); + #pragma warning( push ) + #pragma warning ( disable : 26440 ) // Disable "Can be marked with noexcept" + VOID NotifyDisconnect() override + #pragma warning( pop ) + { + } - VOID - ReferenceRequestHandler( - VOID - ) const; - - virtual - VOID - DereferenceRequestHandler( - VOID - ) const; - -protected: - mutable LONG m_cRefs; - IHttpContext* m_pW3Context; - APPLICATION* m_pApplication; - HTTP_MODULE_ID m_pModuleId; -}; \ No newline at end of file +private: + IHttpContext& m_pHttpContext; + mutable LONG m_cRefs = 1; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/resources.h index 140a573f3f..fdee8b9578 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -3,19 +3,20 @@ #pragma once +#include "aspnetcore_msg.h" + #define IDS_INVALID_PROPERTY 1000 #define IDS_SERVER_ERROR 1001 -#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" -#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" +#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module V2" +#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module V2" -#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 #define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and process '%d' is listening on port '%d'." #define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." #define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' at stage '%s', ErrorCode = '0x%x', assigned port %d, retryCounter '%d'." -#define ASPNETCORE_EVENT_PROCESS_START_FAILURE_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' with multiple retries. The last try of listening port is '%d'. See pervious warnings for details." +#define ASPNETCORE_EVENT_PROCESS_START_FAILURE_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' with multiple retries. The last try of listening port is '%d'. See previous warnings for details." #define ASPNETCORE_EVENT_PROCESS_START_STATUS_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' , ErrorCode = '0x%x', processId '%d', processStatus '%d'." -#define ASPNETCORE_EVENT_PROCESS_START_PORTSETUP_ERROR_MSG L"Application '%s' with physical root '%s' failed to choose listen port '%d' given port rang '%d - %d', EorrorCode = '0x%x'. If environment variable 'ASPNETCORE_PORT' was set, try removing it such that a random port is selected instead." +#define ASPNETCORE_EVENT_PROCESS_START_PORTSETUP_ERROR_MSG L"Application '%s' with physical root '%s' failed to choose listen port '%d' given port range '%d - %d', ErrorCode = '0x%x'. If environment variable 'ASPNETCORE_PORT' was set, try removing it such that a random port is selected instead." #define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" #define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not respond or did not listen on the given port '%d', ErrorCode = '0x%x'" #define ASPNETCORE_EVENT_PROCESS_SHUTDOWN_MSG L"Application '%s' with physical root '%s' shut down process with Id '%d' listening on port '%d'" @@ -23,25 +24,26 @@ #define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." #define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." #define ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown application '%s'." -#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x." +#define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application. %s" +#define ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG L"Application '%s' has shutdown." #define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool." #define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%d' other than the one of running application(s)." +#define ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG L"Could not load configuration. Exception message: %s" #define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'." -#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. First 4KB characters of captured stderr logs on startup:\r\n%s" -#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Last 4KB characters of captured stdout and stderr logs:\r\n%s" -#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Please check the stderr logs for more information." +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. Last 4KB characters of captured stdout and stderr logs:\r\n%s" +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, exit code = '%d'. Please check the stderr logs for more information." #define ASPNETCORE_EVENT_APP_IN_SHUTDOWN_MSG L"Application shutting down." #define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"Application '%s' was recycled after detecting the app_offline file." -#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_REMOVED_MSG L"App_offline.htm has been removed from the application. Application will be recycled." +#define ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG L"Monitoring app_offline.htm failed for application '%s', ErrorCode '0x%x'. " #define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change" #define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process." #define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled" -#define ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG L"Application '%s' was compiled for .NET Framework. Please compile for .NET core to run the inprocess application or change the process mode to out of process. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING_MSG L"Could not find dotnet.exe on the system PATH environment variable for portable application '%s'. Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG L"Could not find the hostfxr directory '%s' in the dotnet directory. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG L"Could not find hostfxr.dll in '%s'. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG L"Could not find application executable in '%s'. ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information." -#define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = %s, ErrorCode = '0x%x'." -#define ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG L"Could not find the aspnetcorerh.dll for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application." -#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the aspnetcorerh.dll for out-of-process application. Please confirm the aspnetcorerh.dll is installed in installed globally for IIS or IISExpress." +#define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'." +#define ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG L"Invoking hostfxr to find the inprocess request handler failed without finding any native dependencies. This most likely means the app is misconfigured, please check the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App that are targeted by the application and are installed on the machine." +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x'. Please check the stderr logs for more information." +#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, exception code = '0x%x'. Last 4KB characters of captured stdout and stderr logs:\r\n%s" +#define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find inprocess request handler. Captured output from invoking hostfxr: %s" +#define ASPNETCORE_EVENT_INPROCESS_RH_REFERENCE_MSG L"Could not find the assembly '%s' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application." +#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress." +#define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully." +#define ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG L"Application '%s' wasn't able to start. %s" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp index 0351feb240..12fcb1d436 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.cpp @@ -1,4 +1,4 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "stdafx.h" +// Do not remove this file. It is used for precompiled header generation diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.h index 69ad058f1f..b17ae43485 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -9,26 +9,9 @@ #include #include -#include +#include #include #include #include -#include "Shlwapi.h" -#include "..\IISLib\hashtable.h" -#include "..\IISLib\stringu.h" -#include "..\IISLib\stringa.h" -#include "..\IISLib\multisz.h" -#include "..\IISLib\dbgutil.h" -#include "..\IISLib\ahutil.h" -#include "..\IISLib\hashfn.h" -#include "SRWLockWrapper.h" -#include "environmentvariablehash.h" -#include "utility.h" -#include "aspnetcoreconfig.h" -#include "application.h" -#include "requesthandler.h" -#include "fx_ver.h" -#include "hostfxr_utility.h" -#include "resources.h" -#include "aspnetcore_msg.h" - +#include +#include diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/sttimer.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/sttimer.h similarity index 99% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/sttimer.h rename to src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/sttimer.h index dfb79e7a6a..26d79d0737 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/sttimer.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/sttimer.h @@ -3,8 +3,10 @@ #pragma once + #ifndef _STTIMER_H #define _STTIMER_H +#include "stringu.h" class STTIMER { @@ -276,4 +278,4 @@ private: BOOL _fUsingHighResolution; }; -#endif // _STTIMER_H \ No newline at end of file +#endif // _STTIMER_H diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.cxx deleted file mode 100644 index cb762f21bc..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.cxx +++ /dev/null @@ -1,656 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include"stdafx.h" - -// static -HRESULT -UTILITY::SplitUrl( - PCWSTR pszDestinationUrl, - BOOL *pfSecure, - STRU *pstrDestination, - STRU *pstrUrl -) -/*++ - -Routine Description: - - Split the URL specified for forwarding into its specific components - The format of the URL looks like - http[s]://destination[:port]/path - when port is omitted, the default port for that specific protocol is used - when host is omitted, it gets the same value as the destination - -Arguments: - - pszDestinationUrl - the url to be split up - pfSecure - SSL to be used in forwarding? - pstrDestination - destination - pDestinationPort - port - pstrUrl - URL - -Return Value: - - HRESULT - ---*/ -{ - HRESULT hr; - - // - // First determine if the target is secure - // - if (_wcsnicmp(pszDestinationUrl, L"http://", 7) == 0) - { - *pfSecure = FALSE; - pszDestinationUrl += 7; - } - else if (_wcsnicmp(pszDestinationUrl, L"https://", 8) == 0) - { - *pfSecure = TRUE; - pszDestinationUrl += 8; - } - else - { - return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); - } - - if (*pszDestinationUrl == L'\0') - { - return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); - } - - // - // Find the 3rd slash corresponding to the url - // - LPCWSTR pszSlash = wcschr(pszDestinationUrl, L'/'); - if (pszSlash == NULL) - { - if (FAILED(hr = pstrUrl->Copy(L"/", 1)) || - FAILED(hr = pstrDestination->Copy(pszDestinationUrl))) - { - return hr; - } - } - else - { - if (FAILED(hr = pstrUrl->Copy(pszSlash)) || - FAILED(hr = pstrDestination->Copy(pszDestinationUrl, - (DWORD)(pszSlash - pszDestinationUrl)))) - { - return hr; - } - } - - return S_OK; -} - -// Change a hexadecimal digit to its numerical equivalent -#define TOHEX( ch ) \ - ((ch) > L'9' ? \ - (ch) >= L'a' ? \ - (ch) - L'a' + 10 : \ - (ch) - L'A' + 10 \ - : (ch) - L'0') - -// static -HRESULT -UTILITY::UnEscapeUrl( - PCWSTR pszUrl, - DWORD cchUrl, - bool fCopyQuery, - STRA * pstrResult -) -{ - HRESULT hr; - CHAR pch[2]; - pch[1] = '\0'; - DWORD cchStart = 0; - DWORD index = 0; - - while (index < cchUrl && - (fCopyQuery || pszUrl[index] != L'?')) - { - switch (pszUrl[index]) - { - case L'%': - if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) - { - if (index > cchStart && - FAILED(hr = pstrResult->AppendW(pszUrl + cchStart, - index - cchStart))) - { - return hr; - } - cchStart = index+3; - - pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + - TOHEX(pszUrl[index+2])); - if (FAILED(hr = pstrResult->Append(pch, 1))) - { - return hr; - } - index += 3; - break; - } - - __fallthrough; - default: - index++; - } - } - - if (index > cchStart) - { - return pstrResult->AppendW(pszUrl + cchStart, - index - cchStart); - } - - return S_OK; -} - -// static -HRESULT -UTILITY::UnEscapeUrl( - PCWSTR pszUrl, - DWORD cchUrl, - STRU * pstrResult -) -{ - HRESULT hr; - WCHAR pch[2]; - pch[1] = L'\0'; - DWORD cchStart = 0; - DWORD index = 0; - bool fInQuery = FALSE; - - while (index < cchUrl) - { - switch (pszUrl[index]) - { - case L'%': - if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) - { - if (index > cchStart && - FAILED(hr = pstrResult->Append(pszUrl + cchStart, - index - cchStart))) - { - return hr; - } - cchStart = index+3; - - pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + - TOHEX(pszUrl[index+2])); - if (FAILED(hr = pstrResult->Append(pch, 1))) - { - return hr; - } - index += 3; - if (pch[0] == L'?') - { - fInQuery = TRUE; - } - break; - } - - index++; - break; - - case L'/': - if (fInQuery) - { - if (index > cchStart && - FAILED(hr = pstrResult->Append(pszUrl + cchStart, - index - cchStart))) - { - return hr; - } - cchStart = index+1; - - if (FAILED(hr = pstrResult->Append(L"\\", 1))) - { - return hr; - } - index += 1; - break; - } - - __fallthrough; - default: - index++; - } - } - - if (index > cchStart) - { - return pstrResult->Append(pszUrl + cchStart, - index - cchStart); - } - - return S_OK; -} - -HRESULT -UTILITY::EscapeAbsPath( - IHttpRequest * pRequest, - STRU * strEscapedUrl -) -{ - HRESULT hr = S_OK; - STRU strAbsPath; - LPCWSTR pszAbsPath = NULL; - LPCWSTR pszFindStr = NULL; - - hr = strAbsPath.Copy( pRequest->GetRawHttpRequest()->CookedUrl.pAbsPath, - pRequest->GetRawHttpRequest()->CookedUrl.AbsPathLength / sizeof(WCHAR) ); - if(FAILED(hr)) - { - goto Finished; - } - - pszAbsPath = strAbsPath.QueryStr(); - pszFindStr = wcschr(pszAbsPath, L'?'); - - while(pszFindStr != NULL) - { - strEscapedUrl->Append( pszAbsPath, pszFindStr - pszAbsPath); - strEscapedUrl->Append(L"%3F"); - pszAbsPath = pszFindStr + 1; - pszFindStr = wcschr(pszAbsPath, L'?'); - } - - strEscapedUrl->Append(pszAbsPath); - strEscapedUrl->Append(pRequest->GetRawHttpRequest()->CookedUrl.pQueryString, - pRequest->GetRawHttpRequest()->CookedUrl.QueryStringLength / sizeof(WCHAR)); - -Finished: - return hr; -} - -// static -bool -UTILITY::IsValidAttributeNameChar( - WCHAR ch -) -{ - // - // Values based on ASP.NET rendering for cookie names. RFC 2965 is not clear - // what the non-special characters are. - // - return ch == L'\t' || (ch > 31 && ch < 127); -} - -// static -bool -UTILITY::FindInMultiString( - PCWSTR pszMultiString, - PCWSTR pszStringToFind -) -{ - while (*pszMultiString != L'\0') - { - if (wcscmp(pszMultiString, pszStringToFind) == 0) - { - return TRUE; - } - pszMultiString += wcslen(pszMultiString) + 1; - } - - return FALSE; -} - -// static -bool -UTILITY::IsValidQueryStringName( - PCWSTR pszName -) -{ - while (*pszName != L'\0') - { - WCHAR c = *pszName; - if (c != L'-' && c != L'_' && c != L'+' && - c != L'.' && c != L'*' && c != L'$' && c != L'%' && c != L',' && - !iswalnum(c)) - { - return FALSE; - } - pszName++; - } - - return TRUE; -} - -// static -bool -UTILITY::IsValidHeaderName( - PCWSTR pszName -) -{ - while (*pszName != L'\0') - { - WCHAR c = *pszName; - if (c != L'-' && c != L'_' && c != L'+' && - c != L'.' && c != L'*' && c != L'$' && c != L'%' - && !iswalnum(c)) - { - return FALSE; - } - pszName++; - } - - return TRUE; -} - -HRESULT -UTILITY::IsPathUnc( - __in LPCWSTR pszPath, - __out BOOL * pfIsUnc -) -{ - HRESULT hr = S_OK; - STRU strTempPath; - - if ( pszPath == NULL || pfIsUnc == NULL ) - { - hr = E_INVALIDARG; - goto Finished; - } - - hr = MakePathCanonicalizationProof( (LPWSTR) pszPath, &strTempPath ); - if ( FAILED(hr) ) - { - goto Finished; - } - - // - // MakePathCanonicalizationProof will map \\?\UNC, \\.\UNC and \\ to \\?\UNC - // - (*pfIsUnc) = ( _wcsnicmp( strTempPath.QueryStr(), L"\\\\?\\UNC\\", 8 /* sizeof \\?\UNC\ */) == 0 ); - -Finished: - - return hr; -} - -HRESULT -UTILITY::ConvertPathToFullPath( - _In_ LPCWSTR pszPath, - _In_ LPCWSTR pszRootPath, - _Out_ STRU* pStruFullPath -) -{ - HRESULT hr = S_OK; - STRU strFileFullPath; - LPWSTR pszFullPath = NULL; - - // if relative path, prefix with root path and then convert to absolute path. - if ( PathIsRelative(pszPath) ) - { - hr = strFileFullPath.Copy(pszRootPath); - if(FAILED(hr)) - { - goto Finished; - } - - if(!strFileFullPath.EndsWith(L"\\")) - { - hr = strFileFullPath.Append(L"\\"); - if(FAILED(hr)) - { - goto Finished; - } - } - } - - hr = strFileFullPath.Append( pszPath ); - if (FAILED(hr)) - { - goto Finished; - } - - pszFullPath = new WCHAR[ strFileFullPath.QueryCCH() + 1]; - if ( pszFullPath == NULL ) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - if(_wfullpath( pszFullPath, - strFileFullPath.QueryStr(), - strFileFullPath.QueryCCH() + 1 ) == NULL ) - { - hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); - goto Finished; - } - - // convert to canonical path - hr = MakePathCanonicalizationProof( pszFullPath, pStruFullPath ); - if (FAILED(hr)) - { - goto Finished; - } - -Finished: - - if ( pszFullPath != NULL ) - { - delete[] pszFullPath; - pszFullPath = NULL; - } - - return hr; -} - -HRESULT -UTILITY::EnsureDirectoryPathExist( - _In_ LPCWSTR pszPath -) -{ - HRESULT hr = S_OK; - STRU struPath; - DWORD dwPosition = 0; - BOOL fDone = FALSE; - BOOL fUnc = FALSE; - - struPath.Copy(pszPath); - hr = IsPathUnc(pszPath, &fUnc); - if (FAILED(hr)) - { - goto Finished; - } - if (fUnc) - { - // "\\?\UNC\" - dwPosition = 8; - } - else if (struPath.IndexOf(L'?', 0) != -1) - { - // sceanrio "\\?\" - dwPosition = 4; - } - while (!fDone) - { - dwPosition = struPath.IndexOf(L'\\', dwPosition + 1); - if (dwPosition == -1) - { - // not found '/' - fDone = TRUE; - goto Finished; - } - else if (dwPosition ==0) - { - hr = ERROR_INTERNAL_ERROR; - goto Finished; - } - else if (struPath.QueryStr()[dwPosition-1] == L':') - { - // skip volume case - continue; - } - else - { - struPath.QueryStr()[dwPosition] = L'\0'; - } - - if (!CreateDirectory(struPath.QueryStr(), NULL) && - ERROR_ALREADY_EXISTS != GetLastError()) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - fDone = TRUE; - goto Finished; - } - struPath.QueryStr()[dwPosition] = L'\\'; - } - -Finished: - return hr; -} - -HRESULT -UTILITY::FindHighestDotNetVersion( - _In_ std::vector vFolders, - _Out_ STRU *pstrResult -) -{ - HRESULT hr = S_OK; - fx_ver_t max_ver(-1, -1, -1); - for (const auto& dir : vFolders) - { - fx_ver_t fx_ver(-1, -1, -1); - if (fx_ver_t::parse(dir, &fx_ver, false)) - { - // TODO using max instead of std::max works - max_ver = max(max_ver, fx_ver); - } - } - - hr = pstrResult->Copy(max_ver.as_str().c_str()); - - // we check FAILED(hr) outside of function - return hr; -} - -BOOL -UTILITY::DirectoryExists( - _In_ STRU *pstrPath -) -{ - WIN32_FILE_ATTRIBUTE_DATA data; - - if (pstrPath->IsEmpty()) - { - return false; - } - - return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); -} - -VOID -UTILITY::FindDotNetFolders( - _In_ PCWSTR pszPath, - _Out_ std::vector *pvFolders -) -{ - HANDLE handle = NULL; - WIN32_FIND_DATAW data = { 0 }; - - handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); - if (handle == INVALID_HANDLE_VALUE) - { - return; - } - - do - { - std::wstring folder(data.cFileName); - pvFolders->push_back(folder); - } while (FindNextFileW(handle, &data)); - - FindClose(handle); -} - -BOOL -UTILITY::CheckIfFileExists( - _In_ PCWSTR pszFilePath -) -{ - HANDLE hFileHandle = INVALID_HANDLE_VALUE; - SECURITY_ATTRIBUTES saAttr; - BOOL fFileExists = FALSE; - - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - hFileHandle = CreateFile(pszFilePath, - GENERIC_READ, - FILE_SHARE_READ, - &saAttr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - - fFileExists = hFileHandle != INVALID_HANDLE_VALUE || GetLastError() == ERROR_SHARING_VIOLATION; - - if (fFileExists) - { - CloseHandle(hFileHandle); - } - - return fFileExists; -} - -VOID -UTILITY::LogEvent( - _In_ HANDLE hEventLog, - _In_ WORD dwEventInfoType, - _In_ DWORD dwEventId, - _In_ LPCWSTR pstrMsg -) -{ - if (hEventLog != NULL) - { - ReportEventW(hEventLog, - dwEventInfoType, - 0, // wCategory - dwEventId, - NULL, // lpUserSid - 1, // wNumStrings - 0, // dwDataSize, - &pstrMsg, - NULL // lpRawData - ); - } - - if (dwEventInfoType == EVENTLOG_ERROR_TYPE) - { - fwprintf(stderr, L"ERROR: %s\n", pstrMsg); - } -} - -VOID -UTILITY::LogEventF( - _In_ HANDLE hEventLog, - _In_ WORD dwEventInfoType, - _In_ DWORD dwEventId, - _In_ LPCWSTR pstrMsg, - ... -) -{ - va_list argsList; - va_start(argsList, pstrMsg); - - STACK_STRU ( strEventMsg, 256 ); - - if (SUCCEEDED(strEventMsg.SafeVsnwprintf( - pstrMsg, - argsList))) - { - UTILITY::LogEvent(hEventLog, - dwEventInfoType, - dwEventId, - strEventMsg.QueryStr()); - } - - va_end( argsList ); -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.h b/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.h deleted file mode 100644 index 830c2a0613..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/utility.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -class UTILITY -{ -public: - - static - HRESULT - SplitUrl( - PCWSTR pszDestinationUrl, - BOOL *pfSecure, - STRU *pstrDestination, - STRU *pstrUrl - ); - - static - HRESULT - UnEscapeUrl( - PCWSTR pszUrl, - DWORD cchUrl, - bool fCopyQuery, - STRA * pstrResult - ); - - static - HRESULT - UnEscapeUrl( - PCWSTR pszUrl, - DWORD cchUrl, - STRU * pstrResult - ); - - static HRESULT - EscapeAbsPath( - IHttpRequest * pRequest, - STRU * strEscapedUrl - ); - - static - bool - IsValidAttributeNameChar( - WCHAR ch - ); - - static - bool - IsValidQueryStringName( - PCWSTR pszName - ); - - static - bool - IsValidHeaderName( - PCWSTR pszName - ); - - static - bool - FindInMultiString( - PCWSTR pszMultiString, - PCWSTR pszStringToFind - ); - - static - HRESULT - IsPathUnc( - __in LPCWSTR pszPath, - __out BOOL * pfIsUnc - ); - - static - HRESULT - ConvertPathToFullPath( - _In_ LPCWSTR pszPath, - _In_ LPCWSTR pszRootPath, - _Out_ STRU* pStrFullPath - ); - - static - HRESULT - EnsureDirectoryPathExist( - _In_ LPCWSTR pszPath - ); - - static - BOOL - DirectoryExists( - _In_ STRU *pstrPath - ); - - static - VOID - FindDotNetFolders( - _In_ PCWSTR pszPath, - _Out_ std::vector *pvFolders - ); - - static - HRESULT - FindHighestDotNetVersion( - _In_ std::vector vFolders, - _Out_ STRU *pstrResult - ); - - static - BOOL - CheckIfFileExists( - PCWSTR pszFilePath - ); - - static - VOID - LogEvent( - _In_ HANDLE hEventLog, - _In_ WORD dwEventInfoType, - _In_ DWORD dwEventId, - _In_ LPCWSTR pstrMsg - ); - - static - VOID - LogEventF( - _In_ HANDLE hEventLog, - _In_ WORD dwEventInfoType, - _In_ DWORD dwEventId, - __in PCWSTR pstrMsg, - ... - ); - -private: - - UTILITY() {} - ~UTILITY() {} -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/DefaultRules.ruleset b/src/IISIntegration/src/AspNetCoreModuleV2/DefaultRules.ruleset new file mode 100644 index 0000000000..1c2bbdaeaf --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/DefaultRules.ruleset @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj index 7c0cca6626..15d4ef1317 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/IISLib.vcxproj @@ -92,6 +92,7 @@ MultiThreadedDebug false true + true Windows @@ -110,6 +111,7 @@ MultiThreadedDebug false true + true Windows @@ -129,11 +131,13 @@ MultiThreaded false true + true Windows true true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true @@ -150,11 +154,13 @@ MultiThreaded false true + true Windows true true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true @@ -175,7 +181,6 @@ - @@ -184,7 +189,7 @@ - + @@ -193,7 +198,7 @@ - + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cpp similarity index 98% rename from src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cpp index d68813edbc..5f2125134e 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/acache.cpp @@ -3,6 +3,9 @@ #include "precomp.h" +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) + LONG ALLOC_CACHE_HANDLER::sm_nFillPattern = 0xACA50000; HANDLE ALLOC_CACHE_HANDLER::sm_hHeap; @@ -440,4 +443,6 @@ Finished: } return fRet; -} \ No newline at end of file +} + +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ahutil.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ahutil.h index 5694b21b7e..d17dc7be30 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ahutil.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ahutil.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once +#include "stringu.h" #include HRESULT @@ -255,4 +256,4 @@ FindNextLocationElement( HRESULT GetSharedConfigEnabled( BOOL * pfIsSharedConfig -); \ No newline at end of file +); diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/buffer.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/buffer.h index 1d68155387..385b73d717 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/buffer.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/buffer.h @@ -4,7 +4,10 @@ #pragma once #include +#include +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) // // BUFFER_T class shouldn't be used directly. Use BUFFER specialization class instead. @@ -269,3 +272,5 @@ C_ASSERT( sizeof(VOID*) <= sizeof(ULONGLONG) ); #define INLINE_BUFFER_INIT( _name ) \ _name( (BYTE*)__aqw##_name, sizeof( __aqw##_name ) ) + +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/dbgutil.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/dbgutil.h index 45c26777a9..9b714e9bbf 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/dbgutil.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/dbgutil.h @@ -7,11 +7,11 @@ #include // -// TODO +// TODO // Using _CrtDbg implementation. If hooking is desired // wrappers should be provided here so that we can reimplement // if neecessary. -// +// // IF_DEBUG/DEBUG FLAGS // // registry configuration @@ -21,19 +21,21 @@ // Debug error levels for DEBUG_FLAGS_VAR. // -#define DEBUG_FLAG_INFO 0x00000001 -#define DEBUG_FLAG_WARN 0x00000002 -#define DEBUG_FLAG_ERROR 0x00000004 +#define DEBUG_FLAG_TRACE 0x00000001 +#define DEBUG_FLAG_INFO 0x00000002 +#define DEBUG_FLAG_WARN 0x00000004 +#define DEBUG_FLAG_ERROR 0x00000008 // // Predefined error level values. These are backwards from the // windows definitions. // +#define DEBUG_FLAGS_TRACE (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN | DEBUG_FLAG_INFO | DEBUG_FLAG_TRACE) #define DEBUG_FLAGS_INFO (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN | DEBUG_FLAG_INFO) #define DEBUG_FLAGS_WARN (DEBUG_FLAG_ERROR | DEBUG_FLAG_WARN) #define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ERROR) -#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) +#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR | DEBUG_FLAG_TRACE) // // Global variables to control tracing. Generally per module @@ -56,7 +58,7 @@ extern DWORD DEBUG_FLAGS_VAR; #define DECLARE_DEBUG_PRINT_OBJECT( _pszLabel_ ) \ PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ - DWORD DEBUG_FLAGS_VAR = DEBUG_FLAGS_ANY; \ + DWORD DEBUG_FLAGS_VAR = 0; \ #define DECLARE_DEBUG_PRINT_OBJECT2( _pszLabel_, _dwLevel_ ) \ PCSTR DEBUG_LABEL_VAR = _pszLabel_; \ diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/multisz.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/multisz.h index f65c151d4f..9473f52033 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/multisz.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/multisz.h @@ -6,6 +6,10 @@ #include "stringu.h" #include "ntassert.h" +#include + +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) /*++ class MULTISZ: @@ -221,5 +225,6 @@ SplitCommaDelimitedString( MULTISZ * pmszList ); -#endif // !_MULTISZ_HXX_ +#pragma warning( pop ) +#endif // !_MULTISZ_HXX_ diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ntassert.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ntassert.h index 6d2f3b9a30..8e19e03239 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ntassert.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/ntassert.h @@ -11,6 +11,10 @@ #undef ASSERT #endif +#ifdef DEBUG + #define DBG 1 +#endif + #if defined( DBG ) && DBG #define SX_ASSERT( _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L#_x ), DbgRaiseAssertionFailure(), FALSE ) ) ) #define SX_ASSERTMSG( _m, _x ) ( (VOID)( ( ( _x ) ) ? TRUE : ( __annotation( L"Debug", L"AssertFail", L##_m ), DbgRaiseAssertionFailure(), FALSE ) ) ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/percpu.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/percpu.h index 5d3c563935..07828830d7 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/percpu.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/percpu.h @@ -3,6 +3,9 @@ #pragma once +#pragma warning( push ) +#pragma warning ( disable : 26451 ) + template class PER_CPU { @@ -302,4 +305,6 @@ Return: *pCacheLineSize = SYSTEM_CACHE_ALIGNMENT_SIZE; return S_OK; -} \ No newline at end of file +} + +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/pudebug.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/pudebug.h deleted file mode 100644 index 7b0e35da0f..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/pudebug.h +++ /dev/null @@ -1,736 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -# ifndef _PUDEBUG_H_ -# define _PUDEBUG_H_ - -#ifndef _NO_TRACING_ -# define _NO_TRACING_ -#endif // _NO_TRACING_ - -/************************************************************ - * Include Headers - ************************************************************/ - -# ifdef __cplusplus -extern "C" { -# endif // __cplusplus - -# include - -# ifndef dllexp -# define dllexp __declspec( dllexport) -# endif // dllexp - -#include - -#ifndef IN_OUT -#define IN_OUT __inout -#endif - -/*********************************************************** - * Macros - ************************************************************/ - -enum PRINT_REASONS { - PrintNone = 0x0, // Nothing to be printed - PrintError = 0x1, // An error message - PrintWarning = 0x2, // A warning message - PrintLog = 0x3, // Just logging. Indicates a trace of where ... - PrintMsg = 0x4, // Echo input message - PrintCritical = 0x5, // Print and Exit - PrintAssertion= 0x6 // Printing for an assertion failure - }; - - -enum DEBUG_OUTPUT_FLAGS { - DbgOutputNone = 0x0, // None - DbgOutputKdb = 0x1, // Output to Kernel Debugger - DbgOutputLogFile = 0x2, // Output to LogFile - DbgOutputTruncate = 0x4, // Truncate Log File if necessary - DbgOutputStderr = 0x8, // Send output to std error - DbgOutputBackup = 0x10, // Make backup of debug file ? - DbgOutputMemory = 0x20, // Dump to memory buffer - DbgOutputAll = 0xFFFFFFFF // All the bits set. - }; - - -# define MAX_LABEL_LENGTH ( 100) - - -// The following flags are used internally to track what level of tracing we -// are currently using. Bitmapped for extensibility. -#define DEBUG_FLAG_ODS 0x00000001 -//#define DEBUG_FLAG_INFO 0x00000002 -//#define DEBUG_FLAG_WARN 0x00000004 -//#define DEBUG_FLAG_ERROR 0x00000008 -// The following are used internally to determine whether to log or not based -// on what the current state is -//#define DEBUG_FLAGS_INFO (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO) -//#define DEBUG_FLAGS_WARN (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN) -//#define DEBUG_FLAGS_ERROR (DEBUG_FLAG_ODS | DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) - -#define DEBUG_FLAGS_ANY (DEBUG_FLAG_INFO | DEBUG_FLAG_WARN | DEBUG_FLAG_ERROR) - -// -// user of DEBUG infrastructure may choose unique variable name for DEBUG_FLAGS -// that's specially useful for cases where DEBUG infrastructure is used within -// static library (static library may prefer to maintain it's own DebugFlags independent -// on the main program it links to -// -#ifndef DEBUG_FLAGS_VAR -#define DEBUG_FLAGS_VAR g_dwDebugFlags -#endif - -extern -#ifdef __cplusplus -"C" -# endif // _cplusplus - DWORD DEBUG_FLAGS_VAR ; // Debugging Flags - -# define DECLARE_DEBUG_VARIABLE() - -# define SET_DEBUG_FLAGS( dwFlags) DEBUG_FLAGS_VAR = dwFlags -# define GET_DEBUG_FLAGS() ( DEBUG_FLAGS_VAR ) - -# define LOAD_DEBUG_FLAGS_FROM_REG(hkey, dwDefault) \ - DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromReg((hkey), (dwDefault)) - -# define LOAD_DEBUG_FLAGS_FROM_REG_STR(pszRegKey, dwDefault) \ - DEBUG_FLAGS_VAR = PuLoadDebugFlagsFromRegStr((pszRegKey), (dwDefault)) - -# define SAVE_DEBUG_FLAGS_IN_REG(hkey, dwDbg) \ - PuSaveDebugFlagsInReg((hkey), (dwDbg)) - -# define DEBUG_IF( arg, s) if ( DEBUG_ ## arg & GET_DEBUG_FLAGS()) { \ - s \ - } else {} - -# define IF_DEBUG( arg) if ( DEBUG_## arg & GET_DEBUG_FLAGS()) - - -/*++ - class DEBUG_PRINTS - - This class is responsible for printing messages to log file / kernel debugger - - Currently the class supports only member functions for char. - ( not unicode-strings). - ---*/ - - -typedef struct _DEBUG_PRINTS { - - CHAR m_rgchLabel[MAX_LABEL_LENGTH]; - CHAR m_rgchLogFilePath[MAX_PATH]; - CHAR m_rgchLogFileName[MAX_PATH]; - HANDLE m_LogFileHandle; - HANDLE m_StdErrHandle; - BOOL m_fInitialized; - BOOL m_fBreakOnAssert; - DWORD m_dwOutputFlags; - VOID *m_pMemoryLog; -} DEBUG_PRINTS, FAR * LPDEBUG_PRINTS; - - -LPDEBUG_PRINTS -PuCreateDebugPrintsObject( - IN const char * pszPrintLabel, - IN DWORD dwOutputFlags); - -// -// frees the debug prints object and closes any file if necessary. -// Returns NULL on success or returns pDebugPrints on failure. -// -LPDEBUG_PRINTS -PuDeleteDebugPrintsObject( - IN_OUT LPDEBUG_PRINTS pDebugPrints); - - -VOID -PuDbgPrint( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN const char * pszFormat, - ...); - // arglist -VOID -PuDbgPrintW( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN const WCHAR * pszFormat, - ...); // arglist - -// PuDbgPrintError is similar to PuDbgPrint() but allows -// one to print error code in friendly manner -VOID -PuDbgPrintError( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN DWORD dwError, - IN const char * pszFormat, - ...); // arglist - -/*++ - PuDbgDump() does not do any formatting of output. - It just dumps the given message onto the debug destinations. ---*/ -VOID -PuDbgDump( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN const char * pszDump - ); - -// -// PuDbgAssertFailed() *must* be __cdecl to properly capture the -// thread context at the time of the failure. -// - -INT -__cdecl -PuDbgAssertFailed( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN const char * pszExpression, - IN const char * pszMessage); - -INT -WINAPI -PuDbgPrintAssertFailed( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName, - IN const char * pszExpression, - IN const char * pszMessage); - -VOID -PuDbgCaptureContext ( - OUT PCONTEXT ContextRecord - ); - -VOID -PuDbgPrintCurrentTime( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFilePath, - IN int nLineNum, - IN const char * pszFunctionName - ); - -VOID -PuSetDbgOutputFlags( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN DWORD dwFlags); - -DWORD -PuGetDbgOutputFlags( - IN const LPDEBUG_PRINTS pDebugPrints); - - -// -// Following functions return Win32 error codes. -// NO_ERROR if success -// - -DWORD -PuOpenDbgPrintFile( - IN_OUT LPDEBUG_PRINTS pDebugPrints, - IN const char * pszFileName, - IN const char * pszPathForFile); - -DWORD -PuReOpenDbgPrintFile( - IN_OUT LPDEBUG_PRINTS pDebugPrints); - -DWORD -PuCloseDbgPrintFile( - IN_OUT LPDEBUG_PRINTS pDebugPrints); - -DWORD -PuOpenDbgMemoryLog( - IN_OUT LPDEBUG_PRINTS pDebugPrints); - -DWORD -PuCloseDbgMemoryLog( - IN_OUT LPDEBUG_PRINTS pDebugPrints); - -DWORD -PuLoadDebugFlagsFromReg(IN HKEY hkey, IN DWORD dwDefault); - -DWORD -PuLoadDebugFlagsFromRegStr(IN LPCSTR pszRegKey, IN DWORD dwDefault); - -DWORD -PuSaveDebugFlagsInReg(IN HKEY hkey, IN DWORD dwDbg); - - -# define PuPrintToKdb( pszOutput) \ - if ( pszOutput != NULL) { \ - OutputDebugString( pszOutput); \ - } else {} - - - -# ifdef __cplusplus -}; -# endif // __cplusplus - -// begin_user_unmodifiable - - - -/*********************************************************** - * Macros - ************************************************************/ - - -extern -#ifdef __cplusplus -"C" -# endif // _cplusplus -DEBUG_PRINTS * g_pDebug; // define a global debug variable - -# if DBG - -// For the CHK build we want ODS enabled. For an explanation of these flags see -// the comment just after the definition of DBG_CONTEXT -# define DECLARE_DEBUG_PRINTS_OBJECT() \ - DEBUG_PRINTS * g_pDebug = NULL; \ - DWORD DEBUG_FLAGS_VAR = DEBUG_FLAG_ERROR; - -#else // !DBG - -# define DECLARE_DEBUG_PRINTS_OBJECT() \ - DEBUG_PRINTS * g_pDebug = NULL; \ - DWORD DEBUG_FLAGS_VAR = 0; - -#endif // !DBG - - -// -// Call the following macro as part of your initialization for program -// planning to use the debugging class. -// -/** DEBUGDEBUG -# define CREATE_DEBUG_PRINT_OBJECT( pszLabel) \ - g_pDebug = PuCreateDebugPrintsObject( pszLabel, DEFAULT_OUTPUT_FLAGS);\ - if ( g_pDebug == NULL) { \ - OutputDebugStringA( "Unable to Create Debug Print Object \n"); \ - } -*/ - -// -// Call the following macro once as part of the termination of program -// which uses the debugging class. -// -# define DELETE_DEBUG_PRINT_OBJECT( ) \ - g_pDebug = PuDeleteDebugPrintsObject( g_pDebug); - - -# define VALID_DEBUG_PRINT_OBJECT() \ - ( ( g_pDebug != NULL) && g_pDebug->m_fInitialized) - - -// -// Use the DBG_CONTEXT without any surrounding braces. -// This is used to pass the values for global DebugPrintObject -// and File/Line information -// -//# define DBG_CONTEXT g_pDebug, __FILE__, __LINE__, __FUNCTION__ - -// The 3 main tracing macros, each one corresponds to a different level of -// tracing - -// The 3 main tracing macros, each one corresponds to a different level of -// tracing -//# define DBGINFO(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrint args; }} -//# define DBGWARN(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrint args; }} -//# define DBGERROR(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrint args; }} - -# define DBGINFOW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_INFO) { PuDbgPrintW args; }} -# define DBGWARNW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_WARN) { PuDbgPrintW args; }} -# define DBGERRORW(args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintW args; }} - - -// -// DBGPRINTF() is printing function ( much like printf) but always called -// with the DBG_CONTEXT as follows -// DBGPRINTF( ( DBG_CONTEXT, format-string, arguments for format list)); -// -# define DBGPRINTF DBGINFO - -// -// DPERROR() is printing function ( much like printf) but always called -// with the DBG_CONTEXT as follows -// DPERROR( ( DBG_CONTEXT, error, format-string, -// arguments for format list)); -// -# define DPERROR( args) {if (DEBUG_FLAGS_VAR & DEBUG_FLAGS_ERROR) { PuDbgPrintError args; }} - -# if DBG - -# define DBG_CODE(s) s /* echoes code in debugging mode */ - -// The same 3 main tracing macros however in this case the macros are only compiled -// into the CHK build. This is necessary because some tracing info used functions or -// variables which are not compiled into the FRE build. -# define CHKINFO(args) { PuDbgPrint args; } -# define CHKWARN(args) { PuDbgPrint args; } -# define CHKERROR(args) { PuDbgPrint args; } - -# define CHKINFOW(args) { PuDbgPrintW args; } -# define CHKWARNW(args) { PuDbgPrintW args; } -# define CHKERRORW(args) { PuDbgPrintW args; } - - -#ifndef DBG_ASSERT -# ifdef _PREFAST_ -# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ -# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ -# define DBG_REQUIRE( exp) ((void) (exp)) -# else // !_PREFAST_ -# define DBG_ASSERT( exp ) \ - ( (VOID)( ( exp ) || ( DebugBreak(), \ - PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, "" ) ) ) ) - -# define DBG_ASSERT_MSG( exp, pszMsg) \ - ( (VOID)( ( exp ) || ( DebugBreak(), \ - PuDbgPrintAssertFailed( DBG_CONTEXT, #exp, pszMsg ) ) ) ) - -# define DBG_REQUIRE( exp ) \ - DBG_ASSERT( exp ) -# endif // !_PREFAST_ -#endif - - -# define DBG_LOG() PuDbgPrint( DBG_CONTEXT, "\n" ) - -# define DBG_OPEN_LOG_FILE( pszFile, pszPath ) \ - PuOpenDbgPrintFile( g_pDebug, (pszFile), (pszPath) ) - -# define DBG_CLOSE_LOG_FILE( ) \ - PuCloseDbgPrintFile( g_pDebug ) - -# define DBG_OPEN_MEMORY_LOG( ) \ - PuOpenDbgMemoryLog( g_pDebug ) - - -# define DBGDUMP( args ) PuDbgDump args - -# define DBGPRINT_CURRENT_TIME() PuDbgPrintCurrentTime( DBG_CONTEXT ) - -# else // !DBG - -# define DBG_CODE(s) ((void)0) /* Do Nothing */ - -# define CHKINFO(args) ((void)0) /* Do Nothing */ -# define CHKWARN(args) ((void)0) /* Do Nothing */ -# define CHKERROR(args) ((void)0) /* Do Nothing */ - -# define CHKINFOW(args) ((void)0) /* Do Nothing */ -# define CHKWARNW(args) ((void)0) /* Do Nothing */ -# define CHKERRORW(args) ((void)0) /* Do Nothing */ - -#ifndef DBG_ASSERT -# define DBG_ASSERT(exp) ((void)0) /* Do Nothing */ - -# define DBG_ASSERT_MSG(exp, pszMsg) ((void)0) /* Do Nothing */ - -# define DBG_REQUIRE( exp) ((void) (exp)) -#endif // !DBG_ASSERT - -# define DBGDUMP( args) ((void)0) /* Do nothing */ - -# define DBG_LOG() ((void)0) /* Do Nothing */ - -# define DBG_OPEN_LOG_FILE( pszFile, pszPath) ((void)0) /* Do Nothing */ - -# define DBG_OPEN_MEMORY_LOG() ((void)0) /* Do Nothing */ - -# define DBG_CLOSE_LOG_FILE() ((void)0) /* Do Nothing */ - -# define DBGPRINT_CURRENT_TIME() ((void)0) /* Do Nothing */ - -# endif // !DBG - - -// end_user_unmodifiable - -// begin_user_unmodifiable - - -#ifdef ASSERT -# undef ASSERT -#endif - - -# define ASSERT( exp) DBG_ASSERT( exp) - - -// end_user_unmodifiable - -// begin_user_modifiable - -// -// Debugging constants consist of two pieces. -// All constants in the range 0x0 to 0x8000 are reserved -// User extensions may include additional constants (bit flags) -// - -# define DEBUG_API_ENTRY 0x00000001L -# define DEBUG_API_EXIT 0x00000002L -# define DEBUG_INIT_CLEAN 0x00000004L -# define DEBUG_ERROR 0x00000008L - - // End of Reserved Range -# define DEBUG_RESERVED 0x00000FFFL - -// end_user_modifiable - - - -/*********************************************************** - * Platform Type related variables and macros - ************************************************************/ - -// -// Enum for product types -// - -typedef enum _PLATFORM_TYPE { - - PtInvalid = 0, // Invalid - PtNtWorkstation = 1, // NT Workstation - PtNtServer = 2, // NT Server - -} PLATFORM_TYPE; - -// -// IISGetPlatformType is the function used to the platform type -// - -extern -#ifdef __cplusplus -"C" -# endif // _cplusplus -PLATFORM_TYPE -IISGetPlatformType( - VOID - ); - -// -// External Macros -// - -#define InetIsNtServer( _pt ) ((_pt) == PtNtServer) -#define InetIsNtWksta( _pt ) ((_pt) == PtNtWorkstation) -#define InetIsValidPT(_pt) ((_pt) != PtInvalid) - -extern -#ifdef __cplusplus -"C" -# endif // _cplusplus -PLATFORM_TYPE g_PlatformType; - - -// Use the DECLARE_PLATFORM_TYPE macro to declare the platform type -#define DECLARE_PLATFORM_TYPE() \ - PLATFORM_TYPE g_PlatformType = PtInvalid; - -// Use the INITIALIZE_PLATFORM_TYPE to init the platform type -// This should typically go inside the DLLInit or equivalent place. -#define INITIALIZE_PLATFORM_TYPE() \ - g_PlatformType = IISGetPlatformType(); - -// -// Additional Macros to use the Platform Type -// - -#define TsIsNtServer( ) InetIsNtServer(g_PlatformType) -#define TsIsNtWksta( ) InetIsNtWksta(g_PlatformType) -#define IISIsValidPlatform() InetIsValidPT(g_PlatformType) -#define IISPlatformType() (g_PlatformType) - - -/*********************************************************** - * Some utility functions for Critical Sections - ************************************************************/ - -// -// IISSetCriticalSectionSpinCount() provides a thunk for the -// original NT4.0sp3 API SetCriticalSectionSpinCount() for CS with Spin counts -// Users of this function should definitely dynlink with kernel32.dll, -// Otherwise errors will surface to a large extent -// -extern -# ifdef __cplusplus -"C" -# endif // _cplusplus -DWORD -IISSetCriticalSectionSpinCount( - LPCRITICAL_SECTION lpCriticalSection, - DWORD dwSpinCount -); - - -// -// Macro for the calls to SetCriticalSectionSpinCount() -// -# define SET_CRITICAL_SECTION_SPIN_COUNT( lpCS, dwSpins) \ - IISSetCriticalSectionSpinCount( (lpCS), (dwSpins)) - -// -// IIS_DEFAULT_CS_SPIN_COUNT is the default value of spins used by -// Critical sections defined within IIS. -// NYI: We should have to switch the individual values based on experiments! -// Current value is an arbitrary choice -// -# define IIS_DEFAULT_CS_SPIN_COUNT (1000) - -// -// Initializes a critical section and sets its spin count -// to IIS_DEFAULT_CS_SPIN_COUNT. Equivalent to -// InitializeCriticalSectionAndSpinCount(lpCS, IIS_DEFAULT_CS_SPIN_COUNT), -// but provides a safe thunking layer for older systems that don't provide -// this API. -// -extern -# ifdef __cplusplus -"C" -# endif // _cplusplus -BOOL -IISInitializeCriticalSection( - LPCRITICAL_SECTION lpCriticalSection -); - -// -// Macro for the calls to InitializeCriticalSection() -// -# define INITIALIZE_CRITICAL_SECTION(lpCS) IISInitializeCriticalSection(lpCS) - -# endif /* _DEBUG_HXX_ */ - -// -// The following macros allow the automatic naming of certain Win32 objects. -// See IIS\SVCS\IISRTL\WIN32OBJ.C for details on the naming convention. -// -// Set IIS_NAMED_WIN32_OBJECTS to a non-zero value to enable named events, -// semaphores, and mutexes. -// - -#if DBG -#define IIS_NAMED_WIN32_OBJECTS 1 -#else -#define IIS_NAMED_WIN32_OBJECTS 0 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -HANDLE -PuDbgCreateEvent( - __in LPSTR FileName, - IN ULONG LineNumber, - __in LPSTR MemberName, - IN PVOID Address, - IN BOOL ManualReset, - IN BOOL InitialState - ); - -HANDLE -PuDbgCreateSemaphore( - __in LPSTR FileName, - IN ULONG LineNumber, - __in LPSTR MemberName, - IN PVOID Address, - IN LONG InitialCount, - IN LONG MaximumCount - ); - -HANDLE -PuDbgCreateMutex( - __in LPSTR FileName, - IN ULONG LineNumber, - __in LPSTR MemberName, - IN PVOID Address, - IN BOOL InitialOwner - ); - -#ifdef __cplusplus -} // extern "C" -#endif - -#if IIS_NAMED_WIN32_OBJECTS - -#define IIS_CREATE_EVENT( membername, address, manual, state ) \ - PuDbgCreateEvent( \ - (LPSTR)__FILE__, \ - (ULONG)__LINE__, \ - (membername), \ - (PVOID)(address), \ - (manual), \ - (state) \ - ) - -#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ - PuDbgCreateSemaphore( \ - (LPSTR)__FILE__, \ - (ULONG)__LINE__, \ - (membername), \ - (PVOID)(address), \ - (initial), \ - (maximum) \ - ) - -#define IIS_CREATE_MUTEX( membername, address, initial ) \ - PuDbgCreateMutex( \ - (LPSTR)__FILE__, \ - (ULONG)__LINE__, \ - (membername), \ - (PVOID)(address), \ - (initial) \ - ) - -#else // !IIS_NAMED_WIN32_OBJECTS - -#define IIS_CREATE_EVENT( membername, address, manual, state ) \ - CreateEventA( \ - NULL, \ - (manual), \ - (state), \ - NULL \ - ) - -#define IIS_CREATE_SEMAPHORE( membername, address, initial, maximum ) \ - CreateSemaphoreA( \ - NULL, \ - (initial), \ - (maximum), \ - NULL \ - ) - -#define IIS_CREATE_MUTEX( membername, address, initial ) \ - CreateMutexA( \ - NULL, \ - (initial), \ - NULL \ - ) - -#endif // IIS_NAMED_WIN32_OBJECTS - - -/************************ End of File ***********************/ - diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/reftrace.c b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/reftrace.c index c1b2e13a6e..877d358c76 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/reftrace.c +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/reftrace.c @@ -3,7 +3,6 @@ #include #include "dbgutil.h" -#include "pudebug.h" #include "reftrace.h" diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringa.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringa.h index 39737f4a69..94ace540f6 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringa.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringa.h @@ -7,6 +7,10 @@ #include "macros.h" #include + +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) + class STRA { @@ -513,3 +517,5 @@ CHAR* InitHelper(__out CHAR (&psz)[size]) STRA name; #define INLINE_STRA_INIT(name) name(InitHelper(__ach##name), sizeof(__ach##name)) + +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.cpp index 15da79a7fe..74f8595482 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.cpp @@ -1,10 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#pragma warning (disable : 4267) #include "precomp.h" +#pragma warning( push ) +#pragma warning ( disable : 4267 ALL_CODE_ANALYSIS_WARNINGS ) + STRU::STRU( VOID ) : m_cchLen( 0 ) @@ -1199,25 +1201,17 @@ STRU::ExpandEnvironmentVariables( __out STRU * pstrExpandedString ) /*++ - Routine Description: - Expand the environment variables in a string - Arguments: - pszString - String with environment variables to expand pstrExpandedString - Receives expanded string on success - Return Value: - HRESULT - --*/ { HRESULT hr = S_OK; DWORD cchNewSize = 0; - if ( pszString == NULL || pstrExpandedString == NULL ) { @@ -1225,7 +1219,6 @@ Return Value: hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto Exit; } - cchNewSize = ExpandEnvironmentStrings( pszString, pstrExpandedString->QueryStr(), pstrExpandedString->QuerySizeCCH() ); @@ -1234,7 +1227,6 @@ Return Value: hr = HRESULT_FROM_WIN32( GetLastError() ); goto Exit; } - if ( cchNewSize > pstrExpandedString->QuerySizeCCH() ) { hr = pstrExpandedString->Resize( @@ -1244,13 +1236,11 @@ Return Value: { goto Exit; } - cchNewSize = ExpandEnvironmentStrings( pszString, pstrExpandedString->QueryStr(), pstrExpandedString->QuerySizeCCH() ); - if ( cchNewSize == 0 || cchNewSize > pstrExpandedString->QuerySizeCCH() ) { @@ -1258,14 +1248,10 @@ Return Value: goto Exit; } } - pstrExpandedString->SyncWithBuffer(); - hr = S_OK; - Exit: - return hr; } -#pragma warning(default:4267) +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.h b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.h index 6f27c5421d..f60f04cfbb 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/stringu.h @@ -4,8 +4,12 @@ #pragma once #include "buffer.h" +#include #include +#pragma warning( push ) +#pragma warning ( disable : ALL_CODE_ANALYSIS_WARNINGS ) + class STRU { @@ -349,13 +353,12 @@ public: __in PCWSTR pwszFormatString, va_list argsList ); - + static HRESULT ExpandEnvironmentVariables( __in PCWSTR pszString, __out STRU * pstrExpandedString ); - private: // @@ -425,3 +428,4 @@ MakePathCanonicalizationProof( IN PCWSTR pszName, OUT STRU * pstrPath ); +#pragma warning( pop ) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/tracelog.c b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/tracelog.c index f7b2da5e43..6c0d080299 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/tracelog.c +++ b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/tracelog.c @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include -#include "pudebug.h" #include "tracelog.h" #include diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/util.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/IISLib/util.cpp similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV2/IISLib/util.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/IISLib/util.cpp diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/HtmlResponses.rc b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/HtmlResponses.rc new file mode 100644 index 0000000000..25a99683c5 Binary files /dev/null and b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/HtmlResponses.rc differ diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp new file mode 100644 index 0000000000..fb9fe8a015 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "InProcessApplicationBase.h" + +InProcessApplicationBase::InProcessApplicationBase( + IHttpServer& pHttpServer, + IHttpApplication& pHttpApplication) + : AppOfflineTrackingApplication(pHttpApplication), + m_fRecycleCalled(FALSE), + m_pHttpServer(pHttpServer) +{ +} + +VOID +InProcessApplicationBase::StopInternal(bool fServerInitiated) +{ + AppOfflineTrackingApplication::StopInternal(fServerInitiated); + + // Stop was initiated by server no need to do anything, server would stop on it's own + if (fServerInitiated) + { + return; + } + + if (!m_pHttpServer.IsCommandLineLaunch()) + { + // IIS scenario. + // We don't actually handle any shutdown logic here. + // Instead, we notify IIS that the process needs to be recycled, which will call + // ApplicationManager->Shutdown(). This will call shutdown on the application. + m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); + } + else + { + // Send WM_QUIT to the main window to initiate graceful shutdown + EnumWindows([](HWND hwnd, LPARAM) -> BOOL + { + DWORD processId; + + if (GetWindowThreadProcessId(hwnd, &processId) && + processId == GetCurrentProcessId() && + GetConsoleWindow() != hwnd) + { + PostMessage(hwnd, WM_QUIT, 0, 0); + return false; + } + + return true; + }, 0); + } +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h new file mode 100644 index 0000000000..5bc2dd41e1 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "AppOfflineTrackingApplication.h" + +typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs + +class InProcessApplicationBase : public AppOfflineTrackingApplication +{ +public: + + InProcessApplicationBase( + IHttpServer& pHttpServer, + IHttpApplication& pHttpApplication); + + ~InProcessApplicationBase() = default; + + VOID StopInternal(bool fServerInitiated) override; + +protected: + BOOL m_fRecycleCalled; + IHttpServer& m_pHttpServer; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp new file mode 100644 index 0000000000..1b3e22e1dc --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "InProcessOptions.h" +#include "InvalidOperationException.h" +#include "EventLog.h" + +HRESULT InProcessOptions::Create( + IHttpServer& pServer, + IHttpApplication& pHttpApplication, + std::unique_ptr& options) +{ + try + { + const WebConfigConfigurationSource configurationSource(pServer.GetAdminManager(), pHttpApplication); + options = std::make_unique(configurationSource); + } + catch (InvalidOperationException& ex) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + ex.as_wstring().c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + catch (std::runtime_error& ex) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + GetUnexpectedExceptionMessage(ex).c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + CATCH_RETURN(); + + return S_OK; +} + +InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSource) : + m_fStdoutLogEnabled(false), + m_fWindowsAuthEnabled(false), + m_fBasicAuthEnabled(false), + m_fAnonymousAuthEnabled(false), + m_dwStartupTimeLimitInMS(INFINITE), + m_dwShutdownTimeLimitInMS(INFINITE) +{ + auto const aspNetCoreSection = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); + m_strArguments = aspNetCoreSection->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); + m_strProcessPath = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + m_fStdoutLogEnabled = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); + m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); + m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); + m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES); + m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000; + m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000; + + const auto basicAuthSection = configurationSource.GetSection(CS_BASIC_AUTHENTICATION_SECTION); + m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false); + + const auto windowsAuthSection = configurationSource.GetSection(CS_WINDOWS_AUTHENTICATION_SECTION); + m_fWindowsAuthEnabled = windowsAuthSection && windowsAuthSection->GetBool(CS_ENABLED).value_or(false); + + const auto anonAuthSection = configurationSource.GetSection(CS_ANONYMOUS_AUTHENTICATION_SECTION); + m_fAnonymousAuthEnabled = anonAuthSection && anonAuthSection->GetBool(CS_ENABLED).value_or(false); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h new file mode 100644 index 0000000000..e299c2033e --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "ConfigurationSource.h" +#include "WebConfigConfigurationSource.h" + +class InProcessOptions: NonCopyable +{ +public: + const std::wstring& + QueryProcessPath() const + { + return m_strProcessPath; + } + + const std::wstring& + QueryArguments() const + { + return m_strArguments; + } + + bool + QueryStdoutLogEnabled() const + { + return m_fStdoutLogEnabled; + } + + const std::wstring& + QueryStdoutLogFile() const + { + return m_struStdoutLogFile; + } + + bool + QueryDisableStartUpErrorPage() const + { + return m_fDisableStartUpErrorPage; + } + + bool + QueryWindowsAuthEnabled() const + { + return m_fWindowsAuthEnabled; + } + + bool + QueryBasicAuthEnabled() const + { + return m_fBasicAuthEnabled; + } + + bool + QueryAnonymousAuthEnabled() const + { + return m_fAnonymousAuthEnabled; + } + + DWORD + QueryStartupTimeLimitInMS() const + { + if (IsDebuggerPresent()) + { + return INFINITE; + } + + return m_dwStartupTimeLimitInMS; + } + + DWORD + QueryShutdownTimeLimitInMS() const + { + if (IsDebuggerPresent()) + { + return INFINITE; + } + + return m_dwShutdownTimeLimitInMS; + } + + const std::vector>& + QueryEnvironmentVariables() const + { + return m_environmentVariables; + } + + InProcessOptions(const ConfigurationSource &configurationSource); + + static + HRESULT InProcessOptions::Create( + IHttpServer& pServer, + IHttpApplication& pHttpApplication, + std::unique_ptr& options); + +private: + std::wstring m_strArguments; + std::wstring m_strProcessPath; + std::wstring m_struStdoutLogFile; + bool m_fStdoutLogEnabled; + bool m_fDisableStartUpErrorPage; + bool m_fWindowsAuthEnabled; + bool m_fBasicAuthEnabled; + bool m_fAnonymousAuthEnabled; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + std::vector> m_environmentVariables; + +protected: + InProcessOptions() = default; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj similarity index 71% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj rename to src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj index 955839f2b9..69435f32d6 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/RequestHandler.vcxproj +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj @@ -23,9 +23,9 @@ 15.0 {D57EA297-6DC2-4BC0-8C91-334863327863} Win32Proj - RequestHandler + InProcessRequestHandler 10.0.15063.0 - RequestHandler + InProcessRequestHandler @@ -73,7 +73,7 @@ - aspnetcorerh + aspnetcorev2_inprocess false @@ -83,15 +83,15 @@ - NotUsing + Use Level4 Disabled WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h $(IntDir)$(TargetName).pch ProgramDatabase MultiThreadedDebug - ..\IISLib;..\CommonLib;.\Inc + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib true true true @@ -103,25 +103,29 @@ true CompileAsCpp true + stdcpp17 + stdafx.h + true Windows true - kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def + UseLinkTimeCodeGeneration - NotUsing + Use Level4 Disabled WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h $(IntDir)$(TargetName).pch ProgramDatabase MultiThreadedDebug - ..\IISLib;..\CommonLib;.\Inc + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib true true true @@ -133,25 +137,29 @@ true CompileAsCpp true + stdcpp17 + stdafx.h + true Windows true - kernel32.lib;user32.lib;advapi32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib Source.def + UseLinkTimeCodeGeneration Level4 - NotUsing + Use MaxSpeed true true WIN32;NDEBUG;_WINDOWS;_USRDLL;REQUESTHANDLER_EXPORTS;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h MultiThreaded - ..\IISLib;..\CommonLib;.\Inc + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib true true true @@ -163,27 +171,32 @@ true CompileAsCpp true + stdcpp17 + stdafx.h + true Windows false true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;winhttp.lib;odbc32.lib;ws2_32.lib;odbccp32.lib;wbemuuid.lib;iphlpapi.lib;pdh.lib;rpcrt4.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + UseLinkTimeCodeGeneration Level4 - NotUsing + Use MaxSpeed true true NDEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - precomp.hxx + stdafx.h MultiThreaded - ..\IISLib;..\CommonLib;.\Inc + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib true true true @@ -195,61 +208,65 @@ true CompileAsCpp true + stdcpp17 + stdafx.h + true Windows false true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) true Source.def - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;rpcrt4.lib;winhttp.lib;pdh.lib;ws2_32.lib;wbemuuid.lib;iphlpapi.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib + UseLinkTimeCodeGeneration - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - + + + + + + + + Create + Create + Create + Create + {55494e58-e061-4c4c-a0a8-837008e72f85} - {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + {09d9d1d6-2951-4e14-bc35-76a23cf9391a} + + + {1533e271-f61b-441b-8b74-59fb61df0552} - + + + + true + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRhStaticHtml.htm b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRhStaticHtml.htm new file mode 100644 index 0000000000..59d6b0dacb --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRhStaticHtml.htm @@ -0,0 +1,83 @@ + + + + + HTTP Error 500.30 - ANCM In-Process Start Failure + + + +

HTTP Error 500.30 - ANCM In-Process Start Failure

+ +

Common causes of this issue:

+
    +
  • The application failed to start
  • +
  • The application started but then stopped
  • +
  • The application started but threw an exception during startup
  • +
+ +

Troubleshooting steps:

+
    +
  • Check the system event log for error messages
  • +
  • Enable logging the application process' stdout messages
  • +
  • Attach a debugger to the application process and inspect
  • +
+ +

+ For more information visit: + %s https://go.microsoft.com/fwlink/?LinkID=2028265 +

+ + + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h new file mode 100644 index 0000000000..171ad4a2ef --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/ShuttingDownApplication.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "InProcessApplicationBase.h" + +class ShuttingDownHandler : public REQUEST_HANDLER +{ +public: + ShuttingDownHandler(IHttpContext* pContext) + : REQUEST_HANDLER(*pContext), + m_pContext(pContext) + { + } + + REQUEST_NOTIFICATION_STATUS ExecuteRequestHandler() override + { + return ServerShutdownMessage(m_pContext); + } + + static REQUEST_NOTIFICATION_STATUS ServerShutdownMessage(IHttpContext * pContext) + { + pContext->GetResponse()->SetStatus(503, "Server has been shutdown", 0, HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); + return RQ_NOTIFICATION_FINISH_REQUEST; + } +private: + IHttpContext * m_pContext; +}; + +class ShuttingDownApplication : public InProcessApplicationBase +{ +public: + ShuttingDownApplication(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication) + : InProcessApplicationBase(pHttpServer, pHttpApplication) + { + } + + ~ShuttingDownApplication() = default; + + HRESULT CreateHandler(IHttpContext * pHttpContext, IREQUEST_HANDLER ** pRequestHandler) override + { + *pRequestHandler = new ShuttingDownHandler(pHttpContext); + return S_OK; + } +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/Source.def b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/Source.def new file mode 100644 index 0000000000..c540402c00 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/Source.def @@ -0,0 +1,5 @@ +LIBRARY aspnetcorev2_inprocess + +EXPORTS + CreateApplication + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h new file mode 100644 index 0000000000..72b26d857c --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionApplication.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "InProcessApplicationBase.h" +#include "ServerErrorHandler.h" +#include "resource.h" + +class StartupExceptionApplication : public InProcessApplicationBase +{ +public: + StartupExceptionApplication( + IHttpServer& pServer, + IHttpApplication& pApplication, + HINSTANCE moduleInstance, + BOOL disableLogs, + HRESULT hr) + : m_disableLogs(disableLogs), + m_HR(hr), + m_moduleInstance(moduleInstance), + InProcessApplicationBase(pServer, pApplication) + { + } + + ~StartupExceptionApplication() = default; + + HRESULT CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler) + { + *pRequestHandler = new ServerErrorHandler(*pHttpContext, 500, 30, "Internal Server Error", m_HR, m_moduleInstance, m_disableLogs, IN_PROCESS_RH_STATIC_HTML); + return S_OK; + } + + std::string& + GetStaticHtml500Content() + { + return html500Page; + } + +private: + std::string html500Page; + BOOL m_disableLogs; + HRESULT m_HR; + HINSTANCE m_moduleInstance; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h new file mode 100644 index 0000000000..1d0f3b7cc9 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/StartupExceptionHandler.h @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "requesthandler.h" +#include "resource.h" +#include "file_utility.h" + + +class StartupExceptionHandler : public REQUEST_HANDLER +{ +public: + + StartupExceptionHandler(IHttpContext& pContext, BOOL disableLogs, HRESULT hr) + : m_pContext(pContext), + m_disableLogs(disableLogs), + m_HR(hr) + { + } + + ~StartupExceptionHandler() + { + } + + REQUEST_NOTIFICATION_STATUS OnExecuteRequestHandler() + { + static std::string s_html500Page = FILE_UTILITY::GetHtml(g_hModule, IN_PROCESS_SHIM_STATIC_HTML); + + WriteStaticResponse(m_pContext, s_html500Page, m_HR, m_disableLogs); + + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; + } + +private: + IHttpContext& m_pContext; + BOOL m_disableLogs; + HRESULT m_HR; +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp new file mode 100644 index 0000000000..eb84da2869 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp @@ -0,0 +1,137 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// dllmain.cpp : Defines the entry point for the DLL application. + +#include + +#include "inprocessapplication.h" +#include "inprocesshandler.h" +#include "requesthandler_config.h" +#include "debugutil.h" +#include "resources.h" +#include "exceptions.h" +#include "ShuttingDownApplication.h" +#include "InProcessOptions.h" +#include "EventLog.h" +#include "WebConfigConfigurationSource.h" +#include "ConfigurationLoadException.h" +#include "StartupExceptionApplication.h" + +DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2_inprocess.dll"); + +BOOL g_fGlobalInitialize = FALSE; +BOOL g_fProcessDetach = FALSE; +SRWLOCK g_srwLockRH; +IHttpServer * g_pHttpServer = NULL; +HINSTANCE g_hWinHttpModule; +HINSTANCE g_hAspNetCoreModule; +HANDLE g_hEventLog = NULL; +bool g_fInProcessApplicationCreated = false; +HINSTANCE g_hServerModule; + +HRESULT +InitializeGlobalConfiguration( + IHttpServer * pServer, + IHttpApplication* pHttpApplication +) +{ + if (!g_fGlobalInitialize) + { + SRWExclusiveLock lock(g_srwLockRH); + + if (!g_fGlobalInitialize) + { + g_pHttpServer = pServer; + RETURN_IF_FAILED(ALLOC_CACHE_HANDLER::StaticInitialize()); + RETURN_IF_FAILED(IN_PROCESS_HANDLER::StaticInitialize()); + + if (pServer->IsCommandLineLaunch()) + { + g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); + } + else + { + g_hEventLog = RegisterEventSource(NULL, ASPNETCORE_EVENT_PROVIDER); + } + + DebugInitializeFromConfig(*pServer, *pHttpApplication); + + g_fGlobalInitialize = TRUE; + } + } + + return S_OK; +} + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved +) +{ + UNREFERENCED_PARAMETER(lpReserved); + g_hServerModule = hModule; + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + InitializeSRWLock(&g_srwLockRH); + DebugInitialize(hModule); + break; + case DLL_PROCESS_DETACH: + g_fProcessDetach = TRUE; + IN_PROCESS_HANDLER::StaticTerminate(); + ALLOC_CACHE_HANDLER::StaticTerminate(); + DebugStop(); + default: + break; + } + return TRUE; +} + +HRESULT +__stdcall +CreateApplication( + _In_ IHttpServer *pServer, + _In_ IHttpApplication *pHttpApplication, + _In_ APPLICATION_PARAMETER *pParameters, + _In_ DWORD nParameters, + _Out_ IAPPLICATION **ppApplication +) +{ + TraceContextScope traceScope(FindParameter("TraceContext", pParameters, nParameters)); + try + { + HRESULT hr = S_OK; + RETURN_IF_FAILED(InitializeGlobalConfiguration(pServer, pHttpApplication)); + + // In process application was already created so another call to CreateApplication + // means that server is shutting does and request arrived in the meantime + if (g_fInProcessApplicationCreated) + { + *ppApplication = new ShuttingDownApplication(*pServer, *pHttpApplication); + return S_OK; + } + + // never create two inprocess applications in one process + g_fInProcessApplicationCreated = true; + + std::unique_ptr inProcessApplication; + if (!FAILED_LOG(hr = IN_PROCESS_APPLICATION::Start(*pServer, *pHttpApplication, pParameters, nParameters, inProcessApplication))) + { + *ppApplication = inProcessApplication.release(); + } + else + { + std::unique_ptr options; + THROW_IF_FAILED(InProcessOptions::Create(*pServer, *pHttpApplication, options)); + // Set the currently running application to a fake application that returns startup exceptions. + auto pErrorApplication = std::make_unique(*pServer, *pHttpApplication, g_hServerModule, options->QueryDisableStartUpErrorPage(), hr); + + RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline()); + *ppApplication = pErrorApplication.release(); + } + return S_OK; + } + CATCH_RETURN(); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp new file mode 100644 index 0000000000..c351dbc4c1 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -0,0 +1,508 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "inprocessapplication.h" +#include "inprocesshandler.h" +#include "hostfxroptions.h" +#include "requesthandler_config.h" +#include "environmentvariablehelpers.h" +#include "exceptions.h" +#include "LoggingHelpers.h" +#include "resources.h" +#include "EventLog.h" +#include "ModuleHelpers.h" + +IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; + +IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( + IHttpServer& pHttpServer, + IHttpApplication& pApplication, + std::unique_ptr pConfig, + APPLICATION_PARAMETER *pParameters, + DWORD nParameters) : + InProcessApplicationBase(pHttpServer, pApplication), + m_Initialized(false), + m_blockManagedCallbacks(true), + m_waitForShutdown(true), + m_pConfig(std::move(pConfig)) +{ + DBG_ASSERT(m_pConfig); + + const auto knownLocation = FindParameter(s_exeLocationParameterName, pParameters, nParameters); + if (knownLocation != nullptr) + { + m_dotnetExeKnownLocation = knownLocation; + } +} + +IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() +{ + s_Application = nullptr; +} + +VOID +IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated) +{ + StopClr(); + InProcessApplicationBase::StopInternal(fServerInitiated); +} + +VOID +IN_PROCESS_APPLICATION::StopClr() +{ + LOG_INFO(L"Stopping CLR"); + + if (!m_blockManagedCallbacks) + { + // We cannot call into managed if the dll is detaching from the process. + // Calling into managed code when the dll is detaching is strictly a bad idea, + // and usually results in an AV saying "The string binding is invalid" + const auto shutdownHandler = m_ShutdownHandler; + + if (!g_fProcessDetach && shutdownHandler != nullptr) + { + shutdownHandler(m_ShutdownHandlerContext); + } + } + + // Signal shutdown + if (m_pShutdownEvent != nullptr) + { + LOG_IF_FAILED(SetEvent(m_pShutdownEvent)); + } + + if (m_workerThread.joinable()) + { + // Worker thread would wait for clr to finish and log error if required + m_workerThread.join(); + } + + s_Application = nullptr; +} + +VOID +IN_PROCESS_APPLICATION::SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ PFN_DISCONNECT_HANDLER disconnect_callback, + _In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + LOG_INFO(L"In-process callbacks set"); + + m_RequestHandler = request_handler; + m_RequestHandlerContext = pvRequstHandlerContext; + m_DisconnectHandler = disconnect_callback; + m_ShutdownHandler = shutdown_handler; + m_ShutdownHandlerContext = pvShutdownHandlerContext; + m_AsyncCompletionHandler = async_completion_handler; + + m_blockManagedCallbacks = false; + m_Initialized = true; + + // Can't check the std err handle as it isn't a critical error + // Initialization complete + EventLog::Info( + ASPNETCORE_EVENT_INPROCESS_START_SUCCESS, + ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG, + QueryApplicationPhysicalPath().c_str()); + + SetEvent(m_pInitializeEvent); +} + +HRESULT +IN_PROCESS_APPLICATION::LoadManagedApplication() +{ + THROW_LAST_ERROR_IF_NULL(m_pInitializeEvent = CreateEvent( + nullptr, // default security attributes + TRUE, // manual reset event + FALSE, // not set + nullptr)); // name + + THROW_LAST_ERROR_IF_NULL(m_pShutdownEvent = CreateEvent( + nullptr, // default security attributes + TRUE, // manual reset event + FALSE, // not set + nullptr)); // name + + LOG_INFO(L"Waiting for initialization"); + + m_workerThread = std::thread([](std::unique_ptr application) + { + LOG_INFO(L"Starting in-process worker thread"); + application->ExecuteApplication(); + LOG_INFO(L"Stopping in-process worker thread"); + }, ::ReferenceApplication(this)); + + const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() }; + + // Wait for shutdown request + const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS()); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + if (waitResult == WAIT_TIMEOUT) + { + // If server wasn't initialized in time shut application down without waiting for CLR thread to exit + m_waitForShutdown = false; + StopClr(); + throw InvalidOperationException(format(L"Managed server didn't initialize after %u ms.", m_pConfig->QueryStartupTimeLimitInMS())); + } + + // WAIT_OBJECT_0 + 1 is the worker thead handle + if (waitResult == WAIT_OBJECT_0 + 1) + { + // Worker thread exited stop + StopClr(); + throw InvalidOperationException(format(L"CLR worker thread exited prematurely")); + } + + THROW_IF_FAILED(StartMonitoringAppOffline()); + + return S_OK; +} + + +void +IN_PROCESS_APPLICATION::ExecuteApplication() +{ + try + { + std::unique_ptr hostFxrOptions; + + auto context = std::make_shared(); + + auto pProc = s_fMainCallback; + if (pProc == nullptr) + { + HMODULE hModule; + // hostfxr should already be loaded by the shim. If not, then we will need + // to load it ourselves by finding hostfxr again. + THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll")); + + // Get the entry point for main + pProc = reinterpret_cast(GetProcAddress(hModule, "hostfxr_main")); + THROW_LAST_ERROR_IF_NULL(pProc); + + THROW_IF_FAILED(HOSTFXR_OPTIONS::Create( + m_dotnetExeKnownLocation, + m_pConfig->QueryProcessPath(), + QueryApplicationPhysicalPath(), + m_pConfig->QueryArguments(), + hostFxrOptions + )); + + hostFxrOptions->GetArguments(context->m_argc, context->m_argv); + THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess()); + } + context->m_pProc = pProc; + + if (m_pLoggerProvider == nullptr) + { + THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider( + m_pConfig->QueryStdoutLogEnabled(), + !m_pHttpServer.IsCommandLineLaunch(), + m_pConfig->QueryStdoutLogFile().c_str(), + QueryApplicationPhysicalPath().c_str(), + m_pLoggerProvider)); + + m_pLoggerProvider->TryStartRedirection(); + } + + // There can only ever be a single instance of .NET Core + // loaded in the process but we need to get config information to boot it up in the + // first place. This is happening in an execute request handler and everyone waits + // until this initialization is done. + // We set a static so that managed code can call back into this instance and + // set the callbacks + s_Application = this; + + //Start CLR thread + m_clrThread = std::thread(ClrThreadEntryPoint, context); + + // Wait for thread exit or shutdown event + const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() }; + + // Wait for shutdown request + const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + LOG_INFOF(L"Starting shutdown sequence %d", waitResult); + + bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1); + // shutdown was signaled + // only wait for shutdown in case of successful startup + if (m_waitForShutdown) + { + const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS()); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + clrThreadExited = clrWaitResult != WAIT_TIMEOUT; + } + + LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited); + + // At this point CLR thread either finished or timed out, abandon it. + m_clrThread.detach(); + + m_pLoggerProvider->TryStopRedirection(); + + if (m_fStopCalled) + { + if (clrThreadExited) + { + EventLog::Info( + ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL, + ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG, + QueryConfigPath().c_str()); + } + else + { + EventLog::Warn( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, + ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG, + QueryConfigPath().c_str()); + } + } + else + { + if (clrThreadExited) + { + UnexpectedThreadExit(*context); + // If the inprocess server was initialized, we need to cause recycle to be called on the worker process. + // in case when it was not initialized we need to keep server running to serve 502 page + if (m_Initialized) + { + QueueStop(); + } + } + } + } + catch (InvalidOperationException& ex) + { + EventLog::Error( + ASPNETCORE_EVENT_LOAD_CLR_FALIURE, + ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + ex.as_wstring().c_str()); + + OBSERVE_CAUGHT_EXCEPTION(); + } + catch (std::runtime_error& ex) + { + EventLog::Error( + ASPNETCORE_EVENT_LOAD_CLR_FALIURE, + ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + GetUnexpectedExceptionMessage(ex).c_str()); + + OBSERVE_CAUGHT_EXCEPTION(); + } +} + +void IN_PROCESS_APPLICATION::QueueStop() +{ + if (m_fStopCalled) + { + return; + } + + LOG_INFO(L"Queueing in-process stop thread"); + + std::thread stoppingThread([](std::unique_ptr application) + { + LOG_INFO(L"Starting in-process stop thread"); + application->Stop(false); + LOG_INFO(L"Stopping in-process stop thread"); + }, ::ReferenceApplication(this)); + + stoppingThread.detach(); +} + +HRESULT IN_PROCESS_APPLICATION::Start( + IHttpServer& pServer, + IHttpApplication& pHttpApplication, + APPLICATION_PARAMETER* pParameters, + DWORD nParameters, + std::unique_ptr& application) +{ + try + { + std::unique_ptr options; + THROW_IF_FAILED(InProcessOptions::Create(pServer, pHttpApplication, options)); + application = std::unique_ptr( + new IN_PROCESS_APPLICATION(pServer, pHttpApplication, std::move(options), pParameters, nParameters)); + THROW_IF_FAILED(application->LoadManagedApplication()); + return S_OK; + } + catch (InvalidOperationException& ex) + { + EventLog::Error( + ASPNETCORE_EVENT_LOAD_CLR_FALIURE, + ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + pHttpApplication.GetApplicationId(), + pHttpApplication.GetApplicationPhysicalPath(), + ex.as_wstring().c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + catch (std::runtime_error& ex) + { + EventLog::Error( + ASPNETCORE_EVENT_LOAD_CLR_FALIURE, + ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, + pHttpApplication.GetApplicationId(), + pHttpApplication.GetApplicationPhysicalPath(), + GetUnexpectedExceptionMessage(ex).c_str()); + + RETURN_CAUGHT_EXCEPTION(); + } + CATCH_RETURN(); +} + +// Required because __try and objects with destructors can not be mixed +void +IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr& context) +{ + __try + { + auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get()); + + LOG_INFOF(L"Managed application exited with code %d", exitCode); + + context->m_exitCode = exitCode; + } + __except(GetExceptionCode() != 0) + { + LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode()); + + context->m_exceptionCode = GetExceptionCode(); + } +} + +// +// Calls hostfxr_main with the hostfxr and application as arguments. +// This method should not access IN_PROCESS_APPLICATION instance as it may be already freed +// in case of startup timeout +// +VOID +IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr &context) +{ + // Keep aspnetcorev2_inprocess.dll loaded while this thread is running + // this is required because thread might be abandoned + HandleWrapper moduleHandle; + ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle); + + ExecuteClr(context); + + FreeLibraryAndExitThread(moduleHandle.release(), 0); +} + +HRESULT +IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess() +{ + auto variables = m_pConfig->QueryEnvironmentVariables(); + auto inputTable = std::unique_ptr(new ENVIRONMENT_VAR_HASH()); + RETURN_IF_FAILED(inputTable->Initialize(37 /*prime*/)); + // Copy environment variables to old style hash table + for (auto & variable : variables) + { + auto pNewEntry = std::unique_ptr(new ENVIRONMENT_VAR_ENTRY()); + RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str())); + RETURN_IF_FAILED(inputTable->InsertRecord(pNewEntry.get())); + } + + ENVIRONMENT_VAR_HASH* pHashTable = NULL; + std::unique_ptr table; + RETURN_IF_FAILED(ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( + inputTable.get(), + m_pConfig->QueryWindowsAuthEnabled(), + m_pConfig->QueryBasicAuthEnabled(), + m_pConfig->QueryAnonymousAuthEnabled(), + &pHashTable)); + + table.reset(pHashTable); + + HRESULT hr = S_OK; + table->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr); + RETURN_IF_FAILED(hr); + + table->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr); + RETURN_IF_FAILED(hr); + + return S_OK; +} + +VOID +IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const +{ + auto content = m_pLoggerProvider->GetStdOutContent(); + + if (context.m_exceptionCode != 0) + { + if (!content.empty()) + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_STDOUT_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + context.m_exceptionCode, + content.c_str()); + } + else + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + context.m_exceptionCode + ); + } + return; + } + + // + // Ungraceful shutdown, try to log an error message. + // This will be a common place for errors as it means the hostfxr_main returned + // or there was an exception. + // + + if (!content.empty()) + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + context.m_exitCode, + content.c_str()); + } + else + { + EventLog::Error( + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, + ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), + context.m_exitCode); + } +} + +HRESULT +IN_PROCESS_APPLICATION::CreateHandler( + _In_ IHttpContext *pHttpContext, + _Out_ IREQUEST_HANDLER **pRequestHandler) +{ + try + { + *pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler); + } + CATCH_RETURN(); + + return S_OK; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h new file mode 100644 index 0000000000..caf16193d6 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -0,0 +1,185 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "InProcessApplicationBase.h" +#include "InProcessOptions.h" +#include "BaseOutputManager.h" + +class IN_PROCESS_HANDLER; +typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext); +typedef VOID(WINAPI * PFN_DISCONNECT_HANDLER) (void *pvManagedHttpContext); +typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); +typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); + +class IN_PROCESS_APPLICATION : public InProcessApplicationBase +{ +public: + IN_PROCESS_APPLICATION( + IHttpServer& pHttpServer, + IHttpApplication& pApplication, + std::unique_ptr pConfig, + APPLICATION_PARAMETER *pParameters, + DWORD nParameters); + + ~IN_PROCESS_APPLICATION(); + + __override + VOID + StopInternal(bool fServerInitiated) override; + + VOID + SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_callback, + _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, + _In_ PFN_DISCONNECT_HANDLER disconnect_callback, + _In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext + ); + + __override + HRESULT + CreateHandler( + _In_ IHttpContext *pHttpContext, + _Out_ IREQUEST_HANDLER **pRequestHandler) + override; + + // Executes the .NET Core process + void + ExecuteApplication(); + + HRESULT + LoadManagedApplication(); + + + void + QueueStop(); + + void + StopIncomingRequests() + { + QueueStop(); + } + + void + StopCallsIntoManaged() + { + m_blockManagedCallbacks = true; + } + + static + VOID SetMainCallback(hostfxr_main_fn mainCallback) + { + s_fMainCallback = mainCallback; + } + + static + IN_PROCESS_APPLICATION* + GetInstance() + { + return s_Application; + } + + const std::wstring& + QueryExeLocation() const + { + return m_dotnetExeKnownLocation; + } + + const InProcessOptions& + QueryConfig() const + { + return *m_pConfig; + } + + bool + QueryBlockCallbacksIntoManaged() const + { + return m_blockManagedCallbacks; + } + + static + HRESULT Start( + IHttpServer& pServer, + IHttpApplication& pHttpApplication, + APPLICATION_PARAMETER* pParameters, + DWORD nParameters, + std::unique_ptr& application); + +private: + struct ExecuteClrContext: std::enable_shared_from_this + { + ExecuteClrContext(): + m_argc(0), + m_pProc(nullptr), + m_exitCode(0), + m_exceptionCode(0) + { + } + + DWORD m_argc; + std::unique_ptr m_argv; + hostfxr_main_fn m_pProc; + + int m_exitCode; + int m_exceptionCode; + }; + + // Thread executing the .NET Core process this might be abandoned in timeout cases + std::thread m_clrThread; + // Thread tracking the CLR thread, this one is always joined on shutdown + std::thread m_workerThread; + // The event that gets triggered when managed initialization is complete + HandleWrapper m_pInitializeEvent; + // The event that gets triggered when worker thread should exit + HandleWrapper m_pShutdownEvent; + + // The request handler callback from managed code + PFN_REQUEST_HANDLER m_RequestHandler; + VOID* m_RequestHandlerContext; + + // The shutdown handler callback from managed code + PFN_SHUTDOWN_HANDLER m_ShutdownHandler; + VOID* m_ShutdownHandlerContext; + + PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler; + PFN_DISCONNECT_HANDLER m_DisconnectHandler; + + std::wstring m_dotnetExeKnownLocation; + + std::atomic_bool m_blockManagedCallbacks; + bool m_Initialized; + bool m_waitForShutdown; + + std::unique_ptr m_pConfig; + + static IN_PROCESS_APPLICATION* s_Application; + + std::unique_ptr m_pLoggerProvider; + + inline static const LPCSTR s_exeLocationParameterName = "InProcessExeLocation"; + + VOID + UnexpectedThreadExit(const ExecuteClrContext& context) const; + + HRESULT + SetEnvironmentVariablesOnWorkerProcess(); + + void + StopClr(); + + static + void + ClrThreadEntryPoint(const std::shared_ptr &context); + + static + void + ExecuteClr(const std::shared_ptr &context); + + // Allows to override call to hostfxr_main with custom callback + // used in testing + inline static hostfxr_main_fn s_fMainCallback = nullptr; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp new file mode 100644 index 0000000000..8fee377a30 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp @@ -0,0 +1,220 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "inprocesshandler.h" +#include "inprocessapplication.h" +#include "IOutputManager.h" +#include "ShuttingDownApplication.h" +#include "ntassert.h" + +ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL; + +IN_PROCESS_HANDLER::IN_PROCESS_HANDLER( + _In_ std::unique_ptr pApplication, + _In_ IHttpContext *pW3Context, + _In_ PFN_REQUEST_HANDLER pRequestHandler, + _In_ void * pRequestHandlerContext, + _In_ PFN_DISCONNECT_HANDLER pDisconnectHandler, + _In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion +): REQUEST_HANDLER(*pW3Context), + m_pManagedHttpContext(nullptr), + m_requestNotificationStatus(RQ_NOTIFICATION_PENDING), + m_fManagedRequestComplete(FALSE), + m_pW3Context(pW3Context), + m_pApplication(std::move(pApplication)), + m_pRequestHandler(pRequestHandler), + m_pRequestHandlerContext(pRequestHandlerContext), + m_pAsyncCompletionHandler(pAsyncCompletion), + m_pDisconnectHandler(pDisconnectHandler), + m_disconnectFired(false) +{ + InitializeSRWLock(&m_srwDisconnectLock); +} + +__override +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_HANDLER::ExecuteRequestHandler() +{ + ::RaiseEvent(m_pW3Context, nullptr); + + if (m_pRequestHandler == NULL) + { + ::RaiseEvent(m_pW3Context, nullptr, RQ_NOTIFICATION_FINISH_REQUEST); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + else if (m_pApplication->QueryBlockCallbacksIntoManaged()) + { + return ServerShutdownMessage(); + } + + auto status = m_pRequestHandler(this, m_pRequestHandlerContext); + ::RaiseEvent(m_pW3Context, nullptr, status); + return status; +} + +__override +REQUEST_NOTIFICATION_STATUS +IN_PROCESS_HANDLER::AsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus +) +{ + + ::RaiseEvent(m_pW3Context, nullptr); + + if (m_fManagedRequestComplete) + { + // means PostCompletion has been called and this is the associated callback. + ::RaiseEvent(m_pW3Context, nullptr, m_requestNotificationStatus); + return m_requestNotificationStatus; + } + if (m_pApplication->QueryBlockCallbacksIntoManaged()) + { + // this can potentially happen in ungraceful shutdown. + // Or something really wrong happening with async completions + // At this point, managed is in a shutting down state and we cannot send a request to it. + return ServerShutdownMessage(); + } + + assert(m_pManagedHttpContext != nullptr); + // Call the managed handler for async completion. + + auto status = m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion); + ::RaiseEvent(m_pW3Context, nullptr, status); + return status; +} + +REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const +{ + ::RaiseEvent(m_pW3Context, nullptr); + return ShuttingDownHandler::ServerShutdownMessage(m_pW3Context); +} + +VOID +IN_PROCESS_HANDLER::NotifyDisconnect() +{ + ::RaiseEvent(m_pW3Context, nullptr); + + if (m_pApplication->QueryBlockCallbacksIntoManaged() || + m_fManagedRequestComplete) + { + return; + } + + // NotifyDisconnect can be called before the m_pManagedHttpContext is set, + // so save that in a bool. + // Don't lock when calling m_pDisconnect to avoid the potential deadlock between this + // and SetManagedHttpContext + void* pManagedHttpContext = nullptr; + { + SRWExclusiveLock lock(m_srwDisconnectLock); + pManagedHttpContext = m_pManagedHttpContext; + m_disconnectFired = true; + } + + if (pManagedHttpContext != nullptr) + { + m_pDisconnectHandler(m_pManagedHttpContext); + } +} + +VOID +IN_PROCESS_HANDLER::IndicateManagedRequestComplete( + VOID +) +{ + m_fManagedRequestComplete = TRUE; + m_pManagedHttpContext = nullptr; + ::RaiseEvent(m_pW3Context, nullptr); +} + +VOID +IN_PROCESS_HANDLER::SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus +) +{ + m_requestNotificationStatus = requestNotificationStatus; +} + +VOID +IN_PROCESS_HANDLER::SetManagedHttpContext( + PVOID pManagedHttpContext +) +{ + { + SRWExclusiveLock lock(m_srwDisconnectLock); + m_pManagedHttpContext = pManagedHttpContext; + } + + if (m_disconnectFired && m_pManagedHttpContext != nullptr) + { + m_pDisconnectHandler(m_pManagedHttpContext); + } +} + +// static +void * IN_PROCESS_HANDLER::operator new(size_t) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc == NULL) + { + return NULL; + } + return sm_pAlloc->Alloc(); +} + +// static +void IN_PROCESS_HANDLER::operator delete(void * pMemory) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc != NULL) + { + sm_pAlloc->Free(pMemory); + } +} + +// static +HRESULT +IN_PROCESS_HANDLER::StaticInitialize(VOID) +/*++ + +Routine Description: + +Global initialization routine for IN_PROCESS_HANDLER + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + sm_pAlloc = new ALLOC_CACHE_HANDLER; + if (sm_pAlloc == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = sm_pAlloc->Initialize(sizeof(IN_PROCESS_HANDLER), + 64); // nThreshold + +Finished: + if (FAILED(hr)) + { + StaticTerminate(); + } + return hr; +} + +// static +void +IN_PROCESS_HANDLER::StaticTerminate(VOID) +{ + if (sm_pAlloc != NULL) + { + delete sm_pAlloc; + sm_pAlloc = NULL; + } +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h new file mode 100644 index 0000000000..20a5192c18 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "requesthandler.h" +#include +#include "iapplication.h" +#include "inprocessapplication.h" + +class IN_PROCESS_APPLICATION; + +class IN_PROCESS_HANDLER : public REQUEST_HANDLER +{ +public: + IN_PROCESS_HANDLER( + _In_ std::unique_ptr pApplication, + _In_ IHttpContext *pW3Context, + _In_ PFN_REQUEST_HANDLER pRequestHandler, + _In_ void * pRequestHandlerContext, + _In_ PFN_DISCONNECT_HANDLER m_DisconnectHandler, + _In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion); + + ~IN_PROCESS_HANDLER() override = default; + + __override + REQUEST_NOTIFICATION_STATUS + ExecuteRequestHandler() override; + + __override + REQUEST_NOTIFICATION_STATUS + AsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ) override; + + __override + VOID + NotifyDisconnect() override; + + IHttpContext* + QueryHttpContext() const + { + return m_pW3Context; + } + + VOID + SetManagedHttpContext( + PVOID pManagedHttpContext + ); + + VOID + IndicateManagedRequestComplete(); + + VOID + SetAsyncCompletionStatus( + REQUEST_NOTIFICATION_STATUS requestNotificationStatus + ); + + static void * operator new(size_t size); + + static void operator delete(void * pMemory); + + static + HRESULT + StaticInitialize(); + + static + void + StaticTerminate(); + +private: + REQUEST_NOTIFICATION_STATUS + ServerShutdownMessage() const; + + PVOID m_pManagedHttpContext; + BOOL m_fManagedRequestComplete; + REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; + IHttpContext* m_pW3Context; + std::unique_ptr m_pApplication; + PFN_REQUEST_HANDLER m_pRequestHandler; + void* m_pRequestHandlerContext; + PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler; + PFN_DISCONNECT_HANDLER m_pDisconnectHandler; + static ALLOC_CACHE_HANDLER * sm_pAlloc; + bool m_disconnectFired; + SRWLOCK m_srwDisconnectLock; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/requesthandler.rc b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessrequesthandler.rc similarity index 92% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/requesthandler.rc rename to src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessrequesthandler.rc index 2cc99c2331..2cddc56709 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/requesthandler.rc +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessrequesthandler.rc @@ -9,7 +9,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#define FileDescription "IIS ASP.NET Core Module Request Handler. Commit: " CommitHash +#define FileDescription "IIS ASP.NET Core Module V2 Request Handler. Commit: " CommitHash ///////////////////////////////////////////////////////////////////////////// // @@ -78,9 +78,9 @@ BEGIN VALUE "CompanyName", "Microsoft" VALUE "FileDescription", FileDescription VALUE "FileVersion", FileVersionStr - VALUE "InternalName", "aspnetcorerh.dll" + VALUE "InternalName", "aspnetcorev2_inprocess.dll" VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" - VALUE "OriginalFilename", "aspnetcorerh.dll" + VALUE "OriginalFilename", "aspnetcorev2_inprocess.dll" VALUE "ProductName", "ASP.NET Core Module Request Handler" VALUE "ProductVersion", ProductVersionStr END diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/managedexports.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp similarity index 78% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/managedexports.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index 85eb5b4d47..2185f975a4 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/managedexports.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -1,28 +1,42 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#include "precomp.hxx" +#include "inprocessapplication.h" +#include "inprocesshandler.h" +#include "requesthandler_config.h" + +extern bool g_fInProcessApplicationCreated; // // Initialization export // EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -VOID +HRESULT register_callbacks( + _In_ IN_PROCESS_APPLICATION* pInProcessApplication, _In_ PFN_REQUEST_HANDLER request_handler, _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, - _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, + _In_ PFN_DISCONNECT_HANDLER disconnect_handler, + _In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler, _In_ VOID* pvRequstHandlerContext, _In_ VOID* pvShutdownHandlerContext ) { - IN_PROCESS_APPLICATION::GetInstance()->SetCallbackHandles( + if (pInProcessApplication == NULL) + { + return E_INVALIDARG; + } + + pInProcessApplication->SetCallbackHandles( request_handler, shutdown_handler, + disconnect_handler, async_completion_handler, pvRequstHandlerContext, pvShutdownHandlerContext ); + + return S_OK; } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT @@ -53,6 +67,7 @@ http_get_server_variable( { PCWSTR pszVariableValue; DWORD cbLength; + HRESULT hr = pInProcessHandler ->QueryHttpContext() ->GetServerVariable(pszVariableName, &pszVariableValue, &cbLength); @@ -69,11 +84,23 @@ http_get_server_variable( hr = E_OUTOFMEMORY; goto Finished; } - Finished: return hr; } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_set_server_variable( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + _In_ PCSTR pszVariableName, + _In_ PCWSTR pszVariableValue +) +{ + return pInProcessHandler + ->QueryHttpContext() + ->SetServerVariable(pszVariableName, pszVariableValue); +} + EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_set_response_status_code( @@ -82,7 +109,8 @@ http_set_response_status_code( _In_ PCSTR pszReason ) { - return pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(statusCode, pszReason); + return pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(statusCode, pszReason, 0, 0, nullptr, + true); // fTrySkipCustomErrors } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT @@ -146,12 +174,12 @@ http_get_completion_info( } // -// todo: we should not rely on IN_PROCESS_APPLICATION::GetInstance() // the signature should be changed. application's based address should be passed in // struct IISConfigurationData { + IN_PROCESS_APPLICATION* pInProcessApplication; BSTR pwzFullApplicationPath; BSTR pwzVirtualApplicationPath; BOOL fWindowsAuthEnabled; @@ -165,21 +193,20 @@ http_get_application_properties( _In_ IISConfigurationData* pIISCofigurationData ) { - ASPNETCORE_CONFIG* pConfiguration = NULL; - IN_PROCESS_APPLICATION* pApplication = IN_PROCESS_APPLICATION::GetInstance(); - - if (pApplication == NULL) + auto pInProcessApplication = IN_PROCESS_APPLICATION::GetInstance(); + if (pInProcessApplication == NULL) { return E_FAIL; } - pConfiguration = pApplication->QueryConfig(); + const auto& pConfiguration = pInProcessApplication->QueryConfig(); - pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr()); - pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); - pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); - pIISCofigurationData->fBasicAuthEnabled = pConfiguration->QueryBasicAuthEnabled(); - pIISCofigurationData->fAnonymousAuthEnable = pConfiguration->QueryAnonymousAuthEnabled(); + pIISCofigurationData->pInProcessApplication = pInProcessApplication; + pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationPhysicalPath().c_str()); + pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationVirtualPath().c_str()); + pIISCofigurationData->fWindowsAuthEnabled = pConfiguration.QueryWindowsAuthEnabled(); + pIISCofigurationData->fBasicAuthEnabled = pConfiguration.QueryBasicAuthEnabled(); + pIISCofigurationData->fAnonymousAuthEnable = pConfiguration.QueryAnonymousAuthEnabled(); return S_OK; } @@ -373,11 +400,6 @@ http_enable_websockets( _In_ IN_PROCESS_HANDLER* pInProcessHandler ) { - //if (!g_fWebSocketSupported) - //{ - // return E_FAIL; - //} - ((IHttpContext3*)pInProcessHandler->QueryHttpContext())->EnableFullDuplex(); ((IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse())->DisableBuffering(); @@ -393,6 +415,27 @@ http_cancel_io( return pInProcessHandler->QueryHttpContext()->CancelIo(); } +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_disable_buffering( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + pInProcessHandler->QueryHttpContext()->GetResponse()->DisableBuffering(); + + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_close_connection( + _In_ IN_PROCESS_HANDLER* pInProcessHandler +) +{ + pInProcessHandler->QueryHttpContext()->GetResponse()->ResetConnection(); + return S_OK; +} + EXTERN_C __MIDL_DECLSPEC_DLLEXPORT HRESULT http_response_set_unknown_header( @@ -434,17 +477,38 @@ http_get_authentication_information( } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT -VOID -http_stop_calls_into_managed() +HRESULT +http_stop_calls_into_managed(_In_ IN_PROCESS_APPLICATION* pInProcessApplication) { - IN_PROCESS_APPLICATION::GetInstance()->StopCallsIntoManaged(); + if (pInProcessApplication == NULL) + { + return E_INVALIDARG; + } + + pInProcessApplication->StopCallsIntoManaged(); + return S_OK; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_stop_incoming_requests(_In_ IN_PROCESS_APPLICATION* pInProcessApplication) +{ + if (pInProcessApplication == NULL) + { + return E_INVALIDARG; + } + + pInProcessApplication->StopIncomingRequests(); + return S_OK; } EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID -http_stop_incoming_requests() +set_main_handler(_In_ hostfxr_main_fn main) { - IN_PROCESS_APPLICATION::GetInstance()->StopIncomingRequests(); + // Allow inprocess application to be recreated as we reuse the same CLR + g_fInProcessApplicationCreated = false; + IN_PROCESS_APPLICATION::SetMainCallback(main); } // End of export diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/resource.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/resource.h new file mode 100644 index 0000000000..61f67c2833 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by HtmlResponses.rc +// +#define IN_PROCESS_RH_STATIC_HTML 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.cpp new file mode 100644 index 0000000000..12fcb1d436 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.cpp @@ -0,0 +1,4 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// Do not remove this file. It is used for precompiled header generation diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.h b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.h new file mode 100644 index 0000000000..77492b07d5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/InProcessRequestHandler/stdafx.h @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#pragma warning( disable : 4091) + +// +// System related headers +// +#define _WINSOCKAPI_ + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// This should remove our issue of compiling for win7 without header files. +// We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. +// Then, we set the version defines back to Windows 7 to for the remainder of the compilation. +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT +#define NTDDI_VERSION 0x06020000 +#define WINVER 0x0602 +#define _WIN32_WINNT 0x0602 +#include +#undef NTDDI_VERSION +#undef WINVER +#undef _WIN32_WINNT + +#include +#include +#include + +#define NTDDI_VERSION 0x06010000 +#define WINVER 0x0601 +#define _WIN32_WINNT 0x0601 + +#ifdef max +#undef max +template inline T max(T a, T b) +{ + return a > b ? a : b; +} +#endif + +#ifdef min +#undef min +template inline T min(T a, T b) +{ + return a < b ? a : b; +} +#endif + + +inline bool IsSpace(char ch) +{ + switch (ch) + { + case 32: // ' ' + case 9: // '\t' + case 10: // '\n' + case 13: // '\r' + case 11: // '\v' + case 12: // '\f' + return true; + default: + return false; + } +} + +extern BOOL g_fAsyncDisconnectAvailable; +extern BOOL g_fEnableReferenceCountTracing; +extern BOOL g_fProcessDetach; +extern SRWLOCK g_srwLockRH; +extern HANDLE g_hEventLog; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/HtmlResponses.rc b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/HtmlResponses.rc new file mode 100644 index 0000000000..231f122fef --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/HtmlResponses.rc @@ -0,0 +1,68 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// HTML +// + +OUT_OF_PROCESS_RH_STATIC_HTML HTML "OutOfProcessRhStaticHtml.htm" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj new file mode 100644 index 0000000000..ca2bc0f4aa --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj @@ -0,0 +1,285 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {7F87406C-A3C8-4139-A68D-E4C344294A67} + Win32Proj + OutOfProcessRequestHandler + 10.0.15063.0 + OutOfProcessRequestHandler + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + aspnetcorev2_outofprocess + + + false + + + false + + + + Use + Level4 + Disabled + WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + stdafx.h + $(IntDir)$(TargetName).pch + ProgramDatabase + MultiThreadedDebug + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + true + stdcpp17 + stdafx.h + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib;winhttp.lib + Source.def + UseLinkTimeCodeGeneration + + + + + Use + Level4 + Disabled + WIN32;_DEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + stdafx.h + $(IntDir)$(TargetName).pch + ProgramDatabase + MultiThreadedDebug + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + true + stdcpp17 + stdafx.h + + + Windows + true + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib;winhttp.lib + Source.def + UseLinkTimeCodeGeneration + + + + + Level4 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;REQUESTHANDLER_EXPORTS;%(PreprocessorDefinitions) + stdafx.h + MultiThreaded + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + true + stdcpp17 + stdafx.h + + + Windows + false + true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + Source.def + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib;winhttp.lib + UseLinkTimeCodeGeneration + + + + + Level4 + Use + MaxSpeed + true + true + NDEBUG;REQUESTHANDLER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + stdafx.h + MultiThreaded + ..\IISLib;..\CommonLib;.\Inc;..\RequestHandlerLib + true + true + true + false + SyncCThrow + 8Bytes + true + false + true + CompileAsCpp + true + true + stdcpp17 + stdafx.h + + + Windows + false + true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + Source.def + kernel32.lib;user32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;ahadmin.lib;ws2_32.lib;iphlpapi.lib;version.lib;Rpcrt4.lib;winhttp.lib + UseLinkTimeCodeGeneration + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + {1533e271-f61b-441b-8b74-59fb61df0552} + + + + + + + + + true + + + + + + + + \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRhStaticHtml.htm b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRhStaticHtml.htm new file mode 100644 index 0000000000..e7d2f04e7c --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRhStaticHtml.htm @@ -0,0 +1,85 @@ + + + + + HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure + + + +

HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure

+ +

Common causes of this issue:

+ +
    +
  • The application process failed to start
  • +
  • The application process started but then stopped
  • +
  • The application process started but failed to listen on the configured port
  • +
+ +

Troubleshooting steps:

+ +
    +
  • Check the system event log for error messages
  • +
  • Enable logging the application process' stdout messages
  • +
  • Attach a debugger to the application process and inspect
  • +
+ +

+ For more information visit: + %s https://go.microsoft.com/fwlink/?LinkID=808681 +

+ + + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/Source.def b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/Source.def new file mode 100644 index 0000000000..37d763da5a --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/Source.def @@ -0,0 +1,5 @@ +LIBRARY aspnetcorev2_outofprocess + +EXPORTS + CreateApplication + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/dllmain.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cpp similarity index 54% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/dllmain.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cpp index 3cfdef0798..981e3acd36 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/dllmain.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/dllmain.cpp @@ -1,10 +1,13 @@ // dllmain.cpp : Defines the entry point for the DLL application. -#include "precomp.hxx" + #include #include +#include "exceptions.h" + +DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2_outofprocess.dll"); BOOL g_fNsiApiNotSupported = FALSE; -BOOL g_fWebSocketSupported = FALSE; +BOOL g_fWebSocketStaticInitialize = FALSE; BOOL g_fEnableReferenceCountTracing = FALSE; BOOL g_fGlobalInitialize = FALSE; BOOL g_fOutOfProcessInitialize = FALSE; @@ -12,17 +15,15 @@ BOOL g_fOutOfProcessInitializeError = FALSE; BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; BOOL g_fProcessDetach = FALSE; DWORD g_OptionalWinHttpFlags = 0; -DWORD g_dwAspNetCoreDebugFlags = 0; -DWORD g_dwDebugFlags = 0; DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; SRWLOCK g_srwLockRH; HINTERNET g_hWinhttpSession = NULL; IHttpServer * g_pHttpServer = NULL; HINSTANCE g_hWinHttpModule; +HINSTANCE g_hOutOfProcessRHModule; HINSTANCE g_hAspNetCoreModule; HANDLE g_hEventLog = NULL; - VOID InitializeGlobalConfiguration( IHttpServer * pServer @@ -55,7 +56,7 @@ InitializeGlobalConfiguration( } if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", 0, KEY_READ, &hKey) == NO_ERROR) @@ -87,19 +88,6 @@ InitializeGlobalConfiguration( { g_fEnableReferenceCountTracing = !!dwData; } - - cbData = sizeof(dwData); - if ((RegQueryValueEx(hKey, - L"DebugFlags", - NULL, - &dwType, - (LPBYTE)&dwData, - &cbData) == NO_ERROR) && - (dwType == REG_DWORD)) - { - g_dwAspNetCoreDebugFlags = dwData; - } - RegCloseKey(hKey); } dwResult = GetExtendedTcpTable(NULL, @@ -113,7 +101,7 @@ InitializeGlobalConfiguration( g_fNsiApiNotSupported = TRUE; } - g_fWebSocketSupported = IsWindows8OrGreater(); + g_fWebSocketStaticInitialize = IsWindows8OrGreater(); g_fGlobalInitialize = TRUE; } @@ -128,50 +116,53 @@ Finished: // Global initialization routine for OutOfProcess // HRESULT -EnsureOutOfProcessInitializtion() +EnsureOutOfProcessInitializtion(IHttpApplication *pHttpApplication) { - DBG_ASSERT(pServer); + DBG_ASSERT(g_pHttpServer); HRESULT hr = S_OK; - BOOL fLocked = FALSE; if (g_fOutOfProcessInitializeError) { - hr = E_NOT_VALID_STATE; - goto Finished; + FINISHED(E_NOT_VALID_STATE); } - if (!g_fOutOfProcessInitialize) + if (g_fOutOfProcessInitialize) { - AcquireSRWLockExclusive(&g_srwLockRH); - fLocked = TRUE; + FINISHED(S_OK); + } + + { + auto lock = SRWExclusiveLock(g_srwLockRH); + if (g_fOutOfProcessInitializeError) { - hr = E_NOT_VALID_STATE; - goto Finished; + FINISHED(E_NOT_VALID_STATE); } if (g_fOutOfProcessInitialize) { // Done by another thread - goto Finished; + FINISHED(S_OK); } + g_fOutOfProcessInitialize = TRUE; + g_hWinHttpModule = GetModuleHandle(TEXT("winhttp.dll")); - g_hAspNetCoreModule = GetModuleHandle(TEXT("aspnetcore.dll")); + g_hAspNetCoreModule = GetModuleHandle(TEXT("aspnetcorev2.dll")); hr = WINHTTP_HELPER::StaticInitialize(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { if (hr == HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)) { - g_fWebSocketSupported = FALSE; + g_fWebSocketStaticInitialize = FALSE; } else { - goto Finished; + FINISHED(hr); } } @@ -180,74 +171,46 @@ EnsureOutOfProcessInitializtion() WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); - if (g_hWinhttpSession == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + FINISHED_LAST_ERROR_IF(g_hWinhttpSession == NULL); // - // Don't set non-blocking callbacks WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS, + // Don't set non-blocking callbacks WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS, // as we will call WinHttpQueryDataAvailable to get response on the same thread // that we received callback from Winhttp on completing sending/forwarding the request - // + // // // Setup the callback function // - if (WinHttpSetStatusCallback(g_hWinhttpSession, + FINISHED_LAST_ERROR_IF(WinHttpSetStatusCallback(g_hWinhttpSession, FORWARDING_HANDLER::OnWinHttpCompletion, (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), - NULL) == WINHTTP_INVALID_STATUS_CALLBACK) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + NULL) == WINHTTP_INVALID_STATUS_CALLBACK); // // Make sure we see the redirects (rather than winhttp doing it // automatically) // DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; - if (!WinHttpSetOption(g_hWinhttpSession, + FINISHED_LAST_ERROR_IF(!WinHttpSetOption(g_hWinhttpSession, WINHTTP_OPTION_REDIRECT_POLICY, &dwRedirectOption, - sizeof(dwRedirectOption))) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + sizeof(dwRedirectOption))); g_dwTlsIndex = TlsAlloc(); - if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + FINISHED_LAST_ERROR_IF(g_dwTlsIndex == TLS_OUT_OF_INDEXES); + FINISHED_IF_FAILED(ALLOC_CACHE_HANDLER::StaticInitialize()); + FINISHED_IF_FAILED(FORWARDING_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing)); + FINISHED_IF_FAILED(WEBSOCKET_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing)); - hr = FORWARDING_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); - if (FAILED(hr)) - { - goto Finished; - } - - hr = WEBSOCKET_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); - if (FAILED(hr)) - { - goto Finished; - } + DebugInitializeFromConfig(*g_pHttpServer, *pHttpApplication); } - Finished: if (FAILED(hr)) { g_fOutOfProcessInitializeError = TRUE; } - if (fLocked) - { - ReleaseSRWLockExclusive(&g_srwLockRH); - } return hr; } @@ -261,11 +224,16 @@ BOOL APIENTRY DllMain(HMODULE hModule, switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: + g_hOutOfProcessRHModule = hModule; DisableThreadLibraryCalls(hModule); InitializeSRWLock(&g_srwLockRH); + DebugInitialize(hModule); break; case DLL_PROCESS_DETACH: g_fProcessDetach = TRUE; + FORWARDING_HANDLER::StaticTerminate(); + ALLOC_CACHE_HANDLER::StaticTerminate(); + DebugStop(); default: break; } @@ -276,94 +244,27 @@ HRESULT __stdcall CreateApplication( _In_ IHttpServer *pServer, - _In_ ASPNETCORE_CONFIG *pConfig, - _Out_ APPLICATION **ppApplication + _In_ IHttpApplication *pHttpApplication, + _In_ APPLICATION_PARAMETER *pParameters, + _In_ DWORD nParameters, + _Out_ IAPPLICATION **ppApplication ) { - HRESULT hr = S_OK; - APPLICATION *pApplication = NULL; + TraceContextScope traceScope(FindParameter("TraceContext", pParameters, nParameters)); - // Initialze some global variables here InitializeGlobalConfiguration(pServer); - if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) - { - pApplication = new IN_PROCESS_APPLICATION(pServer, pConfig); - if (pApplication == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); - goto Finished; - } - } - else if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_OUT_PROCESS) - { - hr = EnsureOutOfProcessInitializtion(); - if (FAILED(hr)) - { - goto Finished; - } + REQUESTHANDLER_CONFIG *pConfig = nullptr; + RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig)); + std::unique_ptr pRequestHandlerConfig(pConfig); - pApplication = new OUT_OF_PROCESS_APPLICATION(pServer, pConfig); - if (pApplication == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); - goto Finished; - } + RETURN_IF_FAILED(EnsureOutOfProcessInitializtion(pHttpApplication)); - hr = ((OUT_OF_PROCESS_APPLICATION*)pApplication)->Initialize(); - if (FAILED(hr)) - { - delete pApplication; - pApplication = NULL; - goto Finished; - } - } - else - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); - goto Finished; - } + std::unique_ptr pApplication = std::make_unique(*pHttpApplication, std::move(pRequestHandlerConfig)); - *ppApplication = pApplication; + RETURN_IF_FAILED(pApplication->Initialize()); + RETURN_IF_FAILED(pApplication->StartMonitoringAppOffline()); -Finished: - return hr; -} - -HRESULT -__stdcall -CreateRequestHandler( - _In_ IHttpContext *pHttpContext, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication, - _Out_ REQUEST_HANDLER **pRequestHandler -) -{ - HRESULT hr = S_OK; - REQUEST_HANDLER* pHandler = NULL; - ASPNETCORE_CONFIG* pConfig = pApplication->QueryConfig(); - DBG_ASSERT(pConfig); - - if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) - { - pHandler = new IN_PROCESS_HANDLER(pHttpContext, pModuleId, pApplication); - } - else if (pConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_OUT_PROCESS) - { - pHandler = new FORWARDING_HANDLER(pHttpContext, pModuleId, pApplication); - } - else - { - return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); - } - - if (pHandler == NULL) - { - hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); - } - else - { - *pRequestHandler = pHandler; - } - return hr; + *ppApplication = pApplication.release(); + return S_OK; } diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwarderconnection.cpp similarity index 66% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwarderconnection.cpp index 99990f938c..ee49e99242 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwarderconnection.cpp @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "forwarderconnection.h" +#include "exceptions.h" FORWARDER_CONNECTION::FORWARDER_CONNECTION( VOID ) : m_cRefs (1), m_hConnection (NULL) -{ +{ } HRESULT @@ -15,38 +16,19 @@ FORWARDER_CONNECTION::Initialize( DWORD dwPort ) { - HRESULT hr = S_OK; - - hr = m_ConnectionKey.Initialize( dwPort ); - if ( FAILED( hr ) ) - { - goto Finished; - } - + RETURN_IF_FAILED(m_ConnectionKey.Initialize( dwPort )); m_hConnection = WinHttpConnect(g_hWinhttpSession, L"127.0.0.1", (USHORT) dwPort, 0); - if (m_hConnection == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - + RETURN_LAST_ERROR_IF_NULL(m_hConnection); // // Since WinHttp will not emit WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING // when closing WebSocket handle on Win8. Register callback at Connect level as a workaround // - if (WinHttpSetStatusCallback(m_hConnection, + RETURN_LAST_ERROR_IF (WinHttpSetStatusCallback(m_hConnection, FORWARDING_HANDLER::OnWinHttpCompletion, WINHTTP_CALLBACK_FLAG_HANDLES, - NULL) == WINHTTP_INVALID_STATUS_CALLBACK) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - -Finished: - - return hr; -} \ No newline at end of file + NULL) == WINHTTP_INVALID_STATUS_CALLBACK); + return S_OK; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwarderconnection.h similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwarderconnection.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwarderconnection.h diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp similarity index 88% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.cpp rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp index 4614b27d34..c8c0640803 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.cpp +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp @@ -1,4 +1,11 @@ -#include "..\precomp.hxx" +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "forwardinghandler.h" +#include "url_utility.h" +#include "exceptions.h" +#include "ServerErrorHandler.h" +#include "resource.h" // Just to be aware of the FORWARDING_HANDLER object size. C_ASSERT(sizeof(FORWARDING_HANDLER) <= 632); @@ -11,17 +18,15 @@ C_ASSERT(sizeof(FORWARDING_HANDLER) <= 632); #define FORWARDING_HANDLER_SIGNATURE ((DWORD)'FHLR') #define FORWARDING_HANDLER_SIGNATURE_FREE ((DWORD)'fhlr') -STRA FORWARDING_HANDLER::sm_pStra502ErrorMsg; ALLOC_CACHE_HANDLER * FORWARDING_HANDLER::sm_pAlloc = NULL; TRACE_LOG * FORWARDING_HANDLER::sm_pTraceLog = NULL; PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; RESPONSE_HEADER_HASH * FORWARDING_HANDLER::sm_pResponseHeaderHash = NULL; FORWARDING_HANDLER::FORWARDING_HANDLER( - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication -) : REQUEST_HANDLER(pW3Context, pModuleId, pApplication), + _In_ IHttpContext *pW3Context, + _In_ std::unique_ptr pApplication +) : REQUEST_HANDLER(*pW3Context), m_Signature(FORWARDING_HANDLER_SIGNATURE), m_RequestStatus(FORWARDER_START), m_fClientDisconnected(FALSE), @@ -39,13 +44,15 @@ FORWARDING_HANDLER::FORWARDING_HANDLER( m_fDoneAsyncCompletion(FALSE), m_fHttpHandleInClose(FALSE), m_fWebSocketHandleInClose(FALSE), - m_fServerResetConn(FALSE) + m_fServerResetConn(FALSE), + m_cRefs(1), + m_pW3Context(pW3Context), + m_pApplication(std::move(pApplication)), + m_fReactToDisconnect(FALSE) { -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::FORWARDING_HANDLER"); -#endif + LOG_TRACE(L"FORWARDING_HANDLER::FORWARDING_HANDLER"); + m_fWebSocketSupported = m_pApplication->QueryWebsocketStatus(); InitializeSRWLock(&m_RequestLock); } @@ -57,20 +64,18 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( // m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::~FORWARDING_HANDLER"); -#endif + LOG_TRACE(L"FORWARDING_HANDLER::~FORWARDING_HANDLER"); + // // RemoveRequest() should already have been called and m_pDisconnect // has been freed or m_pDisconnect was never initialized. // // Disconnect notification cleanup would happen first, before // the FORWARDING_HANDLER instance got removed from m_pSharedhandler list. - // The m_pServer cleanup would happen afterwards, since there may be a + // The m_pServer cleanup would happen afterwards, since there may be a // call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders() - // - DBG_ASSERT(m_pDisconnect == NULL); + // + DBG_ASSERT(!m_fReactToDisconnect); RemoveRequest(); @@ -85,19 +90,17 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER( __override REQUEST_NOTIFICATION_STATUS -FORWARDING_HANDLER::OnExecuteRequestHandler() +FORWARDING_HANDLER::ExecuteRequestHandler() { REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; HRESULT hr = S_OK; BOOL fRequestLocked = FALSE; - BOOL fHandleSet = FALSE; BOOL fFailedToStartKestrel = FALSE; BOOL fSecure = FALSE; HINTERNET hConnect = NULL; IHttpRequest *pRequest = m_pW3Context->GetRequest(); IHttpResponse *pResponse = m_pW3Context->GetResponse(); IHttpConnection *pClientConnection = NULL; - OUT_OF_PROCESS_APPLICATION *pApplication = NULL; PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; SERVER_PROCESS *pServerProcess = NULL; @@ -125,15 +128,14 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() goto Failure; } - pApplication = static_cast (m_pApplication); - if (pApplication == NULL) + if (m_pApplication == NULL) { hr = E_INVALIDARG; goto Failure; } - hr = pApplication->GetProcess(&pServerProcess); - if (FAILED(hr)) + hr = m_pApplication->GetProcess(&pServerProcess); + if (FAILED_LOG(hr)) { fFailedToStartKestrel = TRUE; goto Failure; @@ -158,7 +160,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() // // parse original url // - if (FAILED(hr = UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + if (FAILED_LOG(hr = URL_UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, &fSecure, &strDestination, &strUrl))) @@ -166,7 +168,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() goto Failure; } - if (FAILED(hr = UTILITY::EscapeAbsPath(pRequest, &struEscapedUrl))) + if (FAILED_LOG(hr = URL_UTILITY::EscapeAbsPath(pRequest, &struEscapedUrl))) { goto Failure; } @@ -178,7 +180,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() // // Mark request as websocket if upgrade header is present. // - if (pApplication->QueryConfig()->QueryWebSocketEnabled()) + if (m_fWebSocketSupported) { USHORT cchHeader = 0; PCSTR pszWebSocketHeader = pRequest->GetHeader("Upgrade", &cchHeader); @@ -193,36 +195,12 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() hConnect, &struEscapedUrl, pServerProcess); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Failure; } - // Set client disconnect callback contract with IIS - m_pDisconnect = static_cast( - pClientConnection->GetModuleContextContainer()-> - GetConnectionModuleContext(m_pModuleId)); - if (m_pDisconnect == NULL) - { - m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT(); - if (m_pDisconnect == NULL) - { - hr = E_OUTOFMEMORY; - goto Failure; - } - - hr = pClientConnection->GetModuleContextContainer()-> - SetConnectionModuleContext(m_pDisconnect, - m_pModuleId); - DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); - if (FAILED(hr)) - { - goto Failure; - } - } - - m_pDisconnect->SetHandler(this); - fHandleSet = TRUE; + m_fReactToDisconnect = TRUE; // require lock as client disconnect callback may happen AcquireSRWLockShared(&m_RequestLock); @@ -301,10 +279,9 @@ FORWARDING_HANDLER::OnExecuteRequestHandler() reinterpret_cast(static_cast(this)))) { hr = HRESULT_FROM_WIN32(GetLastError()); -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); -#endif + + LOG_TRACE(L"FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); + // FREB log if (ANCMEvents::ANCM_REQUEST_FORWARD_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) { @@ -336,20 +313,14 @@ Failure: { pResponse->SetStatus(400, "Bad Request", 0, hr); } + else if (hr == E_APPLICATION_EXITING) + { + pResponse->SetStatus(503, "Service Unavailable", 0, S_OK, nullptr, TRUE); + } else if (fFailedToStartKestrel && !m_pApplication->QueryConfig()->QueryDisableStartUpErrorPage()) { - HTTP_DATA_CHUNK DataChunk; - pResponse->SetStatus(502, "Bad Gateway", 5, hr, NULL, TRUE); - pResponse->SetHeader("Content-Type", - "text/html", - (USHORT)strlen("text/html"), - FALSE - ); - - DataChunk.DataChunkType = HttpDataChunkFromMemory; - DataChunk.FromMemory.pBuffer = (PVOID)sm_pStra502ErrorMsg.QueryStr(); - DataChunk.FromMemory.BufferLength = sm_pStra502ErrorMsg.QueryCB(); - pResponse->WriteEntityChunkByReference(&DataChunk); + ServerErrorHandler handler(*m_pW3Context, 502, 5, "Bad Gateway", hr, g_hOutOfProcessRHModule, m_pApplication->QueryConfig()->QueryDisableStartUpErrorPage(), OUT_OF_PROCESS_RH_STATIC_HTML); + handler.ExecuteRequestHandler(); } else { @@ -382,7 +353,7 @@ Finished: __override REQUEST_NOTIFICATION_STATUS -FORWARDING_HANDLER::OnAsyncCompletion( +FORWARDING_HANDLER::AsyncCompletion( DWORD cbCompletion, HRESULT hrCompletionStatus ) @@ -448,10 +419,8 @@ REQUEST_NOTIFICATION_STATUS if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) { -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::OnAsyncCompletion, Send completed for 101 response"); -#endif + LOG_TRACE(L"FORWARDING_HANDLER::OnAsyncCompletion, Send completed for 101 response"); + // // This should be the write completion of the 101 response. // @@ -470,9 +439,9 @@ REQUEST_NOTIFICATION_STATUS InterlockedIncrement(&m_dwHandlers); } - if (FAILED(hr)) + if (FAILED_LOG(hr)) { - // This failure could happen when client disconnect happens or backend server fails + // This failure could happen when client disconnect happens or backend server fails // after websocket upgrade goto Failure; } @@ -507,7 +476,7 @@ REQUEST_NOTIFICATION_STATUS // failure, if there is more data available from WinHTTP, read it // or else ask if there is more. // - if (FAILED(hrCompletionStatus)) + if (FAILED_LOG(hrCompletionStatus)) { hr = hrCompletionStatus; fClientError = TRUE; @@ -515,7 +484,7 @@ REQUEST_NOTIFICATION_STATUS } hr = OnReceivingResponse(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Failure; } @@ -526,7 +495,7 @@ REQUEST_NOTIFICATION_STATUS hr = OnSendingRequest(cbCompletion, hrCompletionStatus, &fClientError); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Failure; } @@ -684,10 +653,7 @@ Finished: // // Do not use this object after dereferencing it, it may be gone. // -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::OnAsyncCompletion Done %d", retVal); -#endif + LOG_TRACEF(L"FORWARDING_HANDLER::OnAsyncCompletion Done %d", retVal); return retVal; } @@ -722,8 +688,8 @@ HRESULT } hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), - 64); // nThreshold - if (FAILED(hr)) + 64); // nThreshold + if (FAILED_LOG(hr)) { goto Finished; } @@ -736,14 +702,14 @@ HRESULT } hr = sm_pResponseHeaderHash->Initialize(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } // Initialize PROTOCOL_CONFIG hr = sm_ProtocolConfig.Initialize(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -753,33 +719,8 @@ HRESULT sm_pTraceLog = CreateRefTraceLog(10000, 0); } - sm_pStra502ErrorMsg.Copy( - " \ - \ - \ - \ - IIS 502.5 Error \ -
\ -

HTTP Error 502.5 - Process Failure

\ -
\ -

Common causes of this issue:

\ -
  • The application process failed to start
  • \ -
  • The application process started but then stopped
  • \ -
  • The application process started but failed to listen on the configured port
\ -
\ -
\ -

Troubleshooting steps:

\ -
  • Check the system event log for error messages
  • \ -
  • Enable logging the application process' stdout messages
  • \ -
  • Attach a debugger to the application process and inspect
\ -

For more information visit: \ - https://go.microsoft.com/fwlink/?LinkID=808681

\ -
\ -
\ -
"); - Finished: - if (FAILED(hr)) + if (FAILED_LOG(hr)) { StaticTerminate(); } @@ -790,8 +731,6 @@ Finished: VOID FORWARDING_HANDLER::StaticTerminate() { - sm_pStra502ErrorMsg.Reset(); - if (sm_pResponseHeaderHash != NULL) { sm_pResponseHeaderHash->Clear(); @@ -812,6 +751,27 @@ FORWARDING_HANDLER::StaticTerminate() } } +// static +void * FORWARDING_HANDLER::operator new(size_t) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc == NULL) + { + return NULL; + } + return sm_pAlloc->Alloc(); +} + +// static +void FORWARDING_HANDLER::operator delete(void * pMemory) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc != NULL) + { + sm_pAlloc->Free(pMemory); + } +} + HRESULT FORWARDING_HANDLER::GetHeaders( _In_ const PROTOCOL_CONFIG * pProtocol, @@ -827,8 +787,8 @@ FORWARDING_HANDLER::GetHeaders( PCSTR pszFinalHeader; USHORT cchCurrentHeader; DWORD cchFinalHeader; - BOOL fSecure = FALSE; // dummy. Used in SplitUrl. Value will not be used - // as ANCM always use http protocol to communicate with backend + BOOL fSecure = FALSE; // dummy. Used in SplitUrl. Value will not be used + // as ANCM always use http protocol to communicate with backend STRU struDestination; STRU struUrl; STACK_STRA(strTemp, 64); @@ -843,12 +803,12 @@ FORWARDING_HANDLER::GetHeaders( // if (!pProtocol->QueryPreserveHostHeader()) { - if (FAILED(hr = UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + if (FAILED_LOG(hr = URL_UTILITY::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, &fSecure, &struDestination, &struUrl)) || - FAILED(hr = strTemp.CopyW(struDestination.QueryStr())) || - FAILED(hr = pRequest->SetHeader(HttpHeaderHost, + FAILED_LOG(hr = strTemp.CopyW(struDestination.QueryStr())) || + FAILED_LOG(hr = pRequest->SetHeader(HttpHeaderHost, strTemp.QueryStr(), static_cast(strTemp.QueryCCH()), TRUE))) // fReplace @@ -858,7 +818,7 @@ FORWARDING_HANDLER::GetHeaders( } // // Strip all headers starting with MS-ASPNETCORE. - // These headers are generated by the asp.net core module and + // These headers are generated by the asp.net core module and // passed to the process it creates. // @@ -889,7 +849,7 @@ FORWARDING_HANDLER::GetHeaders( pServerProcess->QueryGuid(), (USHORT)strlen(pServerProcess->QueryGuid()), TRUE); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -905,7 +865,7 @@ FORWARDING_HANDLER::GetHeaders( HANDLE hTargetTokenHandle = NULL; hr = pServerProcess->SetWindowsAuthToken(m_pW3Context->GetUser()->GetPrimaryToken(), &hTargetTokenHandle); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -924,7 +884,7 @@ FORWARDING_HANDLER::GetHeaders( pszHandleStr, (USHORT)strlen(pszHandleStr), TRUE); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -938,14 +898,14 @@ FORWARDING_HANDLER::GetHeaders( pszCurrentHeader = pRequest->GetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), &cchCurrentHeader); if (pszCurrentHeader != NULL) { - if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || - FAILED(hr = strTemp.Append(", ", 2))) + if (FAILED_LOG(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED_LOG(hr = strTemp.Append(", ", 2))) { return hr; } } - if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_ADDR", + if (FAILED_LOG(hr = m_pW3Context->GetServerVariable("REMOTE_ADDR", &pszFinalHeader, &cchFinalHeader))) { @@ -954,16 +914,16 @@ FORWARDING_HANDLER::GetHeaders( if (pRequest->GetRawHttpRequest()->Address.pRemoteAddress->sa_family == AF_INET6) { - if (FAILED(hr = strTemp.Append("[", 1)) || - FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader)) || - FAILED(hr = strTemp.Append("]", 1))) + if (FAILED_LOG(hr = strTemp.Append("[", 1)) || + FAILED_LOG(hr = strTemp.Append(pszFinalHeader, cchFinalHeader)) || + FAILED_LOG(hr = strTemp.Append("]", 1))) { return hr; } } else { - if (FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + if (FAILED_LOG(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) { return hr; } @@ -971,21 +931,21 @@ FORWARDING_HANDLER::GetHeaders( if (pProtocol->QueryIncludePortInXForwardedFor()) { - if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_PORT", + if (FAILED_LOG(hr = m_pW3Context->GetServerVariable("REMOTE_PORT", &pszFinalHeader, &cchFinalHeader))) { return hr; } - if (FAILED(hr = strTemp.Append(":", 1)) || - FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + if (FAILED_LOG(hr = strTemp.Append(":", 1)) || + FAILED_LOG(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) { return hr; } } - if (FAILED(hr = pRequest->SetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), + if (FAILED_LOG(hr = pRequest->SetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), strTemp.QueryStr(), static_cast(strTemp.QueryCCH()), TRUE))) // fReplace @@ -1008,19 +968,19 @@ FORWARDING_HANDLER::GetHeaders( pszCurrentHeader = pRequest->GetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), &cchCurrentHeader); if (pszCurrentHeader != NULL) { - if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || - FAILED(hr = strTemp.Append(", ", 2))) + if (FAILED_LOG(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED_LOG(hr = strTemp.Append(", ", 2))) { return hr; } } - if (FAILED(hr = strTemp.Append(pszScheme))) + if (FAILED_LOG(hr = strTemp.Append(pszScheme))) { return hr; } - if (FAILED(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), + if (FAILED_LOG(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), strTemp.QueryStr(), (USHORT)strTemp.QueryCCH(), TRUE))) @@ -1039,7 +999,7 @@ FORWARDING_HANDLER::GetHeaders( else { // Resize the buffer large enough to hold the encoded certificate info - if (FAILED(hr = strTemp.Resize( + if (FAILED_LOG(hr = strTemp.Resize( 1 + (pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize + 2) / 3 * 4))) { return hr; @@ -1053,7 +1013,7 @@ FORWARDING_HANDLER::GetHeaders( NULL); strTemp.SyncWithBuffer(); - if (FAILED(hr = pRequest->SetHeader( + if (FAILED_LOG(hr = pRequest->SetHeader( pProtocol->QueryClientCertName()->QueryStr(), strTemp.QueryStr(), static_cast(strTemp.QueryCCH()), @@ -1078,7 +1038,7 @@ FORWARDING_HANDLER::GetHeaders( hr = m_pW3Context->GetServerVariable("ALL_RAW", ppszHeaders, pcchHeaders); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -1098,6 +1058,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( HRESULT hr = S_OK; PCWSTR pszVersion = NULL; PCSTR pszVerb; + DWORD dwTimeout = INFINITE; STACK_STRU(strVerb, 32); // @@ -1105,7 +1066,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( // we will fill them when sending the request) // pszVerb = pRequest->GetHttpMethod(); - if (FAILED(hr = strVerb.CopyA(pszVerb))) + if (FAILED_LOG(hr = strVerb.CopyA(pszVerb))) { goto Finished; } @@ -1118,7 +1079,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( "HTTP_VERSION", &pszVersion, &cchUnused); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -1138,11 +1099,16 @@ FORWARDING_HANDLER::CreateWinHttpRequest( goto Finished; } + if (!pServerProcess->IsDebuggerAttached()) + { + dwTimeout = pProtocol->QueryTimeout(); + } + if (!WinHttpSetTimeouts(m_hRequest, - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout(), - pProtocol->QueryTimeout())) + dwTimeout, //resolve timeout + dwTimeout, // connect timeout + dwTimeout, // send timeout + dwTimeout)) // receive timeout { hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; @@ -1201,7 +1167,7 @@ FORWARDING_HANDLER::CreateWinHttpRequest( pServerProcess, &m_pszHeaders, &m_cchHeaders); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -1297,10 +1263,8 @@ None dwInternetStatus); } -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, GetCurrentThreadId(), m_pW3Context); -#endif + LOG_TRACEF(L"FORWARDING_HANDLER::OnWinHttpCompletionInternal %x -- %d --%p\n", dwInternetStatus, GetCurrentThreadId(), m_pW3Context); + // // Exclusive lock on the winhttp handle to protect from a client disconnect/ // server stop closing the handle while we are using it. @@ -1480,7 +1444,7 @@ None // // Handle failure code for switch statement above. // - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Failure; } @@ -1728,7 +1692,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( goto Finished; } } - else if (FAILED(hr)) + else if (FAILED_LOG(hr)) { *pfClientError = TRUE; goto Finished; @@ -1808,7 +1772,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( } } - if (FAILED(hr = strHeaders.CopyW( + if (FAILED_LOG(hr = strHeaders.CopyW( reinterpret_cast(bufHeaderBuffer.QueryPtr())))) { goto Finished; @@ -1825,13 +1789,13 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( if (!strHeaders.IsEmpty() && strHeaders.QueryStr()[strHeaders.QueryCCH() - 1] != '\n') { hr = strHeaders.Append("\r\n"); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } } - if (FAILED(hr = SetStatusAndHeaders( + if (FAILED_LOG(hr = SetStatusAndHeaders( strHeaders.QueryStr(), strHeaders.QueryCCH()))) { @@ -1856,7 +1820,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( NULL, NULL); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { *pfAnotherCompletionExpected = FALSE; } @@ -1988,7 +1952,7 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( Chunk.DataChunkType = HttpDataChunkFromMemory; Chunk.FromMemory.pBuffer = m_pEntityBuffer; Chunk.FromMemory.BufferLength = dwStatusInformationLength; - if (FAILED(hr = pResponse->WriteEntityChunkByReference(&Chunk))) + if (FAILED_LOG(hr = pResponse->WriteEntityChunkByReference(&Chunk))) { goto Finished; } @@ -2001,8 +1965,8 @@ FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( // hr = pResponse->Flush(TRUE, // fAsync TRUE, // fMoreData - NULL); // pcbSent - if (FAILED(hr)) + NULL); // pcbSent + if (FAILED_LOG(hr)) { goto Finished; } @@ -2323,10 +2287,10 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // // Copy the status description // - if (FAILED(hr = strHeaderValue.Copy( + if (FAILED_LOG(hr = strHeaderValue.Copy( pchStatus, (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || - FAILED(hr = pResponse->SetStatus(uStatus, + FAILED_LOG(hr = pResponse->SetStatus(uStatus, strHeaderValue.QueryStr(), 0, S_OK, @@ -2388,7 +2352,7 @@ FORWARDING_HANDLER::SetStatusAndHeaders( // // Copy the header name // - if (FAILED(hr = strHeaderName.Copy( + if (FAILED_LOG(hr = strHeaderName.Copy( pszHeaders + index, (DWORD)(pchEndofHeaderName - pszHeaders) - index))) { @@ -2424,7 +2388,7 @@ FORWARDING_HANDLER::SetStatusAndHeaders( { strHeaderValue.Reset(); } - else if (FAILED(hr = strHeaderValue.Copy( + else if (FAILED_LOG(hr = strHeaderValue.Copy( pszHeaders + index, (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) { @@ -2474,7 +2438,7 @@ FORWARDING_HANDLER::SetStatusAndHeaders( static_cast(strHeaderValue.QueryCCH()), TRUE); // fReplace } - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -2492,7 +2456,7 @@ FORWARDING_HANDLER::SetStatusAndHeaders( if (m_fDoReverseRewriteHeaders) { hr = DoReverseRewrite(pResponse); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { return hr; } @@ -2539,17 +2503,17 @@ FORWARDING_HANDLER::DoReverseRewrite( pszEndHost = strchr(pszStartHost, '/'); - if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || - FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + if (FAILED_LOG(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED_LOG(hr = strTemp.Append(m_pszOriginalHostHeader))) { return hr; } if (pszEndHost != NULL && - FAILED(hr = strTemp.Append(pszEndHost))) + FAILED_LOG(hr = strTemp.Append(pszEndHost))) { return hr; } - if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation, + if (FAILED_LOG(hr = pResponse->SetHeader(HttpHeaderContentLocation, strTemp.QueryStr(), static_cast(strTemp.QueryCCH()), TRUE))) @@ -2578,17 +2542,17 @@ Location: pszEndHost = strchr(pszStartHost, '/'); - if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || - FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + if (FAILED_LOG(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED_LOG(hr = strTemp.Append(m_pszOriginalHostHeader))) { return hr; } if (pszEndHost != NULL && - FAILED(hr = strTemp.Append(pszEndHost))) + FAILED_LOG(hr = strTemp.Append(pszEndHost))) { return hr; } - if (FAILED(hr = pResponse->SetHeader(HttpHeaderLocation, + if (FAILED_LOG(hr = pResponse->SetHeader(HttpHeaderLocation, strTemp.QueryStr(), static_cast(strTemp.QueryCCH()), TRUE))) @@ -2653,9 +2617,9 @@ SetCookie: pszEndHost++; } - if (FAILED(hr = strTemp.Copy(pszHeader, static_cast(pszStartHost - pszHeader))) || - FAILED(hr = strTemp.Append(m_pszOriginalHostHeader)) || - FAILED(hr = strTemp.Append(pszEndHost))) + if (FAILED_LOG(hr = strTemp.Copy(pszHeader, static_cast(pszStartHost - pszHeader))) || + FAILED_LOG(hr = strTemp.Append(m_pszOriginalHostHeader)) || + FAILED_LOG(hr = strTemp.Append(pszEndHost))) { return hr; } @@ -2681,21 +2645,16 @@ FORWARDING_HANDLER::RemoveRequest( VOID ) { - ASYNC_DISCONNECT_CONTEXT * pDisconnect; - pDisconnect = (ASYNC_DISCONNECT_CONTEXT *)InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL); - if (pDisconnect != NULL) - { - pDisconnect->ResetHandler(); - pDisconnect = NULL; - } + m_fReactToDisconnect = FALSE; } VOID -FORWARDING_HANDLER::TerminateRequest( - bool fClientInitiated -) +FORWARDING_HANDLER::NotifyDisconnect() { - UNREFERENCED_PARAMETER(fClientInitiated); + if (!m_fReactToDisconnect) + { + return; + } BOOL fLocked = FALSE; if (TlsGetValue(g_dwTlsIndex) != this) @@ -2712,14 +2671,11 @@ FORWARDING_HANDLER::TerminateRequest( // a winhttp callback on the same thread and we donot want to // acquire the lock again -#ifdef DEBUG - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "FORWARDING_HANDLER::TerminateRequest %d --%p\n", GetCurrentThreadId(), m_pW3Context); -#endif // DEBUG + LOG_TRACEF(L"FORWARDING_HANDLER::TerminateRequest %d --%p\n", GetCurrentThreadId(), m_pW3Context); if (!m_fHttpHandleInClose) { - m_fClientDisconnected = fClientInitiated; + m_fClientDisconnected = true; } if (fLocked) diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h similarity index 89% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h index 427540f2d3..cc855dfcf2 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/forwardinghandler.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h @@ -1,9 +1,11 @@ #pragma once extern DWORD g_OptionalWinHttpFlags; +extern HINSTANCE g_hOutOfProcessRHModule; extern HINSTANCE g_hWinHttpModule; extern HINSTANCE g_hAspNetCoreModule; +class OUT_OF_PROCESS_APPLICATION; enum FORWARDING_REQUEST_STATUS { @@ -20,20 +22,19 @@ class FORWARDING_HANDLER : public REQUEST_HANDLER { public: FORWARDING_HANDLER( - - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication); + _In_ IHttpContext *pW3Context, + _In_ std::unique_ptr pApplication + ); ~FORWARDING_HANDLER(); __override REQUEST_NOTIFICATION_STATUS - OnExecuteRequestHandler(); + ExecuteRequestHandler(); __override REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( + AsyncCompletion( DWORD cbCompletion, HRESULT hrCompletionStatus ); @@ -68,9 +69,11 @@ public: StaticTerminate(); VOID - TerminateRequest( - bool fClientInitiated - ); + NotifyDisconnect() override; + + static void * operator new(size_t size); + + static void operator delete(void * pMemory); private: @@ -177,6 +180,7 @@ private: FORWARDING_REQUEST_STATUS m_RequestStatus; BOOL m_fWebSocketEnabled; + BOOL m_fWebSocketSupported; BOOL m_fResponseHeadersReceivedAndSet; BOOL m_fResetConnection; BOOL m_fDoReverseRewriteHeaders; @@ -193,7 +197,7 @@ private: volatile BOOL m_fDoneAsyncCompletion; volatile BOOL m_fHasError; // - // WinHttp may hit AV under race if handle got closed more than once simultaneously + // WinHttp may hit AV under race if handle got closed more than once simultaneously // Use two bool variables to guard // volatile BOOL m_fHttpHandleInClose; @@ -215,7 +219,6 @@ private: DWORD m_cMinBufferLimit; ULONGLONG m_cContentLength; WEBSOCKET_HANDLER * m_pWebSocket; - ASYNC_DISCONNECT_CONTEXT * m_pDisconnect; BYTE * m_pEntityBuffer; static const SIZE_T INLINE_ENTITY_BUFFERS = 8; @@ -230,4 +233,9 @@ private: static TRACE_LOG * sm_pTraceLog; static STRA sm_pStra502ErrorMsg; + + mutable LONG m_cRefs; + IHttpContext* m_pW3Context; + std::unique_ptr m_pApplication; + bool m_fReactToDisconnect; }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocessrequesthandler.rc b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocessrequesthandler.rc new file mode 100644 index 0000000000..654496a8c5 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outofprocessrequesthandler.rc @@ -0,0 +1,119 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "version.h" +#include "..\CommonLib\resources.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#define FileDescription "IIS ASP.NET Core Module V2 Request Handler. Commit: " CommitHash + +///////////////////////////////////////////////////////////////////////////// +// +// 11 +// + +//1 11 +//BEGIN +// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, +// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, +// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, +// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, +// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, +// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, +// 0x0000, 0x0000 +//END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\CommonLib\resources.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FileVersion + PRODUCTVERSION ProductVersion + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Microsoft" + VALUE "FileDescription", FileDescription + VALUE "FileVersion", FileVersionStr + VALUE "InternalName", "aspnetcorev2_outofprocess.dll" + VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" + VALUE "OriginalFilename", "aspnetcorev2_outofprocess.dll" + VALUE "ProductName", "ASP.NET Core Module Request Handler" + VALUE "ProductVersion", ProductVersionStr + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" + IDS_SERVER_ERROR "There was a connection error while trying to route the request." +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp new file mode 100644 index 0000000000..22d0da50c9 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.cpp @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "outprocessapplication.h" + +#include "SRWExclusiveLock.h" +#include "exceptions.h" + +OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( + IHttpApplication& pApplication, + std::unique_ptr pConfig) : + AppOfflineTrackingApplication(pApplication), + m_fWebSocketSupported(WEBSOCKET_STATUS::WEBSOCKET_UNKNOWN), + m_pConfig(std::move(pConfig)) +{ + m_pProcessManager = NULL; +} + +OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() +{ + SRWExclusiveLock lock(m_stateLock); + if (m_pProcessManager != NULL) + { + m_pProcessManager->Shutdown(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} + +HRESULT +OUT_OF_PROCESS_APPLICATION::Initialize( +) +{ + if (m_pProcessManager == NULL) + { + m_pProcessManager = new PROCESS_MANAGER(); + RETURN_IF_FAILED(m_pProcessManager->Initialize()); + } + return S_OK; +} + +HRESULT +OUT_OF_PROCESS_APPLICATION::GetProcess( + _Out_ SERVER_PROCESS **ppServerProcess +) +{ + return m_pProcessManager->GetProcess(m_pConfig.get(), QueryWebsocketStatus(), ppServerProcess); +} + +__override +VOID +OUT_OF_PROCESS_APPLICATION::StopInternal(bool fServerInitiated) +{ + AppOfflineTrackingApplication::StopInternal(fServerInitiated); + + if (m_pProcessManager != NULL) + { + m_pProcessManager->Shutdown(); + } +} + +HRESULT +OUT_OF_PROCESS_APPLICATION::CreateHandler( + _In_ IHttpContext *pHttpContext, + _Out_ IREQUEST_HANDLER **pRequestHandler) +{ + IREQUEST_HANDLER* pHandler = NULL; + + //add websocket check here + if (m_fWebSocketSupported == WEBSOCKET_STATUS::WEBSOCKET_UNKNOWN) + { + SetWebsocketStatus(pHttpContext); + } + + pHandler = new FORWARDING_HANDLER(pHttpContext, ::ReferenceApplication(this)); + *pRequestHandler = pHandler; + return S_OK; +} + +VOID +OUT_OF_PROCESS_APPLICATION::SetWebsocketStatus( + IHttpContext* pHttpContext +) +{ + // Even though the applicationhost.config file contains the websocket element, + // the websocket module may still not be enabled. + PCWSTR pszTempWebsocketValue; + DWORD cbLength; + + if (FAILED_LOG(pHttpContext->GetServerVariable("WEBSOCKET_VERSION", &pszTempWebsocketValue, &cbLength))) + { + m_fWebSocketSupported = WEBSOCKET_STATUS::WEBSOCKET_NOT_SUPPORTED; + } + else + { + m_fWebSocketSupported = WEBSOCKET_STATUS::WEBSOCKET_SUPPORTED; + } +} + +BOOL +OUT_OF_PROCESS_APPLICATION::QueryWebsocketStatus() const +{ + return m_fWebSocketSupported == WEBSOCKET_STATUS::WEBSOCKET_SUPPORTED; +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h new file mode 100644 index 0000000000..43eca31f95 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/outprocessapplication.h @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "AppOfflineTrackingApplication.h" + +class OUT_OF_PROCESS_APPLICATION : public AppOfflineTrackingApplication +{ + enum WEBSOCKET_STATUS + { + WEBSOCKET_UNKNOWN = 0, + WEBSOCKET_NOT_SUPPORTED, + WEBSOCKET_SUPPORTED, + }; + +public: + OUT_OF_PROCESS_APPLICATION( + IHttpApplication& pApplication, + std::unique_ptr pConfig); + + __override + ~OUT_OF_PROCESS_APPLICATION() override; + + HRESULT + Initialize(); + + HRESULT + GetProcess( + _Out_ SERVER_PROCESS **ppServerProcess + ); + + __override + VOID + StopInternal(bool fServerInitiated) + override; + + __override + HRESULT + CreateHandler( + _In_ IHttpContext *pHttpContext, + _Out_ IREQUEST_HANDLER **pRequestHandler) + override; + + BOOL + QueryWebsocketStatus() + const; + + REQUESTHANDLER_CONFIG* QueryConfig() + { + return m_pConfig.get(); + } + +private: + + VOID SetWebsocketStatus(IHttpContext *pHttpContext); + + PROCESS_MANAGER * m_pProcessManager; + IHttpServer *m_pHttpServer; + + WEBSOCKET_STATUS m_fWebSocketSupported; + std::unique_ptr m_pConfig; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp similarity index 53% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp index 7e8c58462a..71d106fb77 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.cpp @@ -1,7 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "processmanager.h" +#include "EventLog.h" +#include "exceptions.h" +#include "SRWSharedLock.h" volatile BOOL PROCESS_MANAGER::sm_fWSAStartupDone = FALSE; @@ -10,28 +13,21 @@ PROCESS_MANAGER::Initialize( VOID ) { - HRESULT hr = S_OK; WSADATA wsaData; int result; - BOOL fLocked = FALSE; if( !sm_fWSAStartupDone ) { - AcquireSRWLockExclusive( &m_srwLock ); - fLocked = TRUE; + auto lock = SRWExclusiveLock(m_srwLock); if( !sm_fWSAStartupDone ) { if( (result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0 ) { - hr = HRESULT_FROM_WIN32( result ); - goto Finished; + RETURN_HR(HRESULT_FROM_WIN32( result )); } sm_fWSAStartupDone = TRUE; } - - ReleaseSRWLockExclusive( &m_srwLock ); - fLocked = FALSE; } m_dwRapidFailTickStart = GetTickCount(); @@ -50,83 +46,39 @@ PROCESS_MANAGER::Initialize( CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); - if( m_hNULHandle == INVALID_HANDLE_VALUE ) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF( m_hNULHandle == INVALID_HANDLE_VALUE ); } -Finished: - - if(fLocked) - { - ReleaseSRWLockExclusive( &m_srwLock ); - } - - return hr; + return S_OK; } PROCESS_MANAGER::~PROCESS_MANAGER() { - AcquireSRWLockExclusive(&m_srwLock); - - //if( m_ppServerProcessList != NULL ) - //{ - // for( DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) - // { - // if( m_ppServerProcessList[i] != NULL ) - // { - // m_ppServerProcessList[i]->DereferenceServerProcess(); - // m_ppServerProcessList[i] = NULL; - // } - // } - - // delete[] m_ppServerProcessList; - // m_ppServerProcessList = NULL; - //} - - //if( m_hNULHandle != NULL ) - //{ - // CloseHandle( m_hNULHandle ); - // m_hNULHandle = NULL; - //} - - //if( sm_fWSAStartupDone ) - //{ - // WSACleanup(); - // sm_fWSAStartupDone = FALSE; - //} - - ReleaseSRWLockExclusive(&m_srwLock); } HRESULT PROCESS_MANAGER::GetProcess( - _In_ ASPNETCORE_CONFIG *pConfig, - _Out_ SERVER_PROCESS **ppServerProcess + _In_ REQUESTHANDLER_CONFIG *pConfig, + _In_ BOOL fWebsocketSupported, + _Out_ SERVER_PROCESS **ppServerProcess ) { - HRESULT hr = S_OK; - BOOL fSharedLock = FALSE; - BOOL fExclusiveLock = FALSE; DWORD dwProcessIndex = 0; - SERVER_PROCESS *pSelectedServerProcess = NULL; + std::unique_ptr pSelectedServerProcess; + + if (InterlockedCompareExchange(&m_lStopping, 1L, 1L) == 1L) + { + RETURN_IF_FAILED(E_APPLICATION_EXITING); + } if (!m_fServerProcessListReady) { - AcquireSRWLockExclusive(&m_srwLock); - fExclusiveLock = TRUE; + auto lock = SRWExclusiveLock(m_srwLock); if (!m_fServerProcessListReady) { m_dwProcessesPerApplication = pConfig->QueryProcessesPerApplication(); m_ppServerProcessList = new SERVER_PROCESS*[m_dwProcessesPerApplication]; - if (m_ppServerProcessList == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } for (DWORD i = 0; i < m_dwProcessesPerApplication; ++i) { @@ -134,35 +86,30 @@ PROCESS_MANAGER::GetProcess( } } m_fServerProcessListReady = TRUE; - ReleaseSRWLockExclusive(&m_srwLock); - fExclusiveLock = FALSE; } - AcquireSRWLockShared(&m_srwLock); - fSharedLock = TRUE; - - // - // round robin through to the next available process. - // - dwProcessIndex = (DWORD)InterlockedIncrement64((LONGLONG*)&m_dwRouteToProcessIndex); - dwProcessIndex = dwProcessIndex % m_dwProcessesPerApplication; - - if (m_ppServerProcessList[dwProcessIndex] != NULL && - m_ppServerProcessList[dwProcessIndex]->IsReady()) { - *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; - goto Finished; - } + auto lock = SRWSharedLock(m_srwLock); - ReleaseSRWLockShared(&m_srwLock); - fSharedLock = FALSE; + // + // round robin through to the next available process. + // + dwProcessIndex = (DWORD)InterlockedIncrement64((LONGLONG*)&m_dwRouteToProcessIndex); + dwProcessIndex = dwProcessIndex % m_dwProcessesPerApplication; + + if (m_ppServerProcessList[dwProcessIndex] != NULL && + m_ppServerProcessList[dwProcessIndex]->IsReady()) + { + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + return S_OK; + } + } // should make the lock per process so that we can start processes simultaneously ? if (m_ppServerProcessList[dwProcessIndex] == NULL || !m_ppServerProcessList[dwProcessIndex]->IsReady()) { - AcquireSRWLockExclusive(&m_srwLock); - fExclusiveLock = TRUE; + auto lock = SRWExclusiveLock(m_srwLock); if (m_ppServerProcessList[dwProcessIndex] != NULL) { @@ -179,7 +126,7 @@ PROCESS_MANAGER::GetProcess( // server is already up and ready to serve requests. //m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; - goto Finished; + return S_OK; } } @@ -188,28 +135,19 @@ PROCESS_MANAGER::GetProcess( // // rapid fails per minute exceeded, do not create new process. // - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, + EventLog::Info( ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED, ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG, pConfig->QueryRapidFailsPerMinute()); - hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); - goto Finished; + RETURN_HR(HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED)); } if (m_ppServerProcessList[dwProcessIndex] == NULL) { - pSelectedServerProcess = new SERVER_PROCESS(); - if (pSelectedServerProcess == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - - hr = pSelectedServerProcess->Initialize( + pSelectedServerProcess = std::make_unique(); + RETURN_IF_FAILED(pSelectedServerProcess->Initialize( this, //ProcessManager pConfig->QueryProcessPath(), // pConfig->QueryArguments(), // @@ -220,55 +158,23 @@ PROCESS_MANAGER::GetProcess( pConfig->QueryAnonymousAuthEnabled(), pConfig->QueryEnvironmentVariables(), pConfig->QueryStdoutLogEnabled(), - pConfig->QueryWebSocketEnabled(), + fWebsocketSupported, pConfig->QueryStdoutLogFile(), pConfig->QueryApplicationPhysicalPath(), // physical path pConfig->QueryApplicationPath(), // app path pConfig->QueryApplicationVirtualPath() // App relative virtual path - ); - if (FAILED(hr)) - { - goto Finished; - } - - hr = pSelectedServerProcess->StartProcess(); - if (FAILED(hr)) - { - goto Finished; - } + )); + RETURN_IF_FAILED(pSelectedServerProcess->StartProcess()); } if (!pSelectedServerProcess->IsReady()) { - hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); - goto Finished; + RETURN_HR(HRESULT_FROM_WIN32(ERROR_CREATE_FAILED)); } - m_ppServerProcessList[dwProcessIndex] = pSelectedServerProcess; - pSelectedServerProcess = NULL; - + m_ppServerProcessList[dwProcessIndex] = pSelectedServerProcess.release(); } *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; -Finished: - - if (fSharedLock) - { - ReleaseSRWLockShared(&m_srwLock); - fSharedLock = FALSE; - } - - if (fExclusiveLock) - { - ReleaseSRWLockExclusive(&m_srwLock); - fExclusiveLock = FALSE; - } - - if (pSelectedServerProcess != NULL) - { - delete pSelectedServerProcess; - pSelectedServerProcess = NULL; - } - - return hr; + return S_OK; } diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.h similarity index 92% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.h index 9523e8a819..764e77d595 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/processmanager.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/processmanager.h @@ -30,8 +30,9 @@ public: HRESULT GetProcess( - _In_ ASPNETCORE_CONFIG *pConfig, - _Out_ SERVER_PROCESS **ppServerProcess + _In_ REQUESTHANDLER_CONFIG *pConfig, + _In_ BOOL fWebsocketEnabled, + _Out_ SERVER_PROCESS **ppServerProcess ); HANDLE @@ -87,6 +88,16 @@ public: ReleaseSRWLockExclusive( &m_srwLock ); } + VOID + Shutdown( + ) + { + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + ShutdownAllProcesses(); + } + } + VOID IncrementRapidFailCount( VOID @@ -102,6 +113,7 @@ public: m_dwProcessesPerApplication( 1 ), m_dwRouteToProcessIndex( 0 ), m_fServerProcessListReady(FALSE), + m_lStopping(0), m_cRefs( 1 ) { m_ppServerProcessList = NULL; @@ -192,4 +204,5 @@ private: volatile static BOOL sm_fWSAStartupDone; volatile BOOL m_fServerProcessListReady; -}; \ No newline at end of file + volatile LONG m_lStopping; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.cpp similarity index 57% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.cpp index 9faebab82a..d0e44b5bd3 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.cpp @@ -1,48 +1,32 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "protocolconfig.h" +#include "exceptions.h" HRESULT PROTOCOL_CONFIG::Initialize() { - HRESULT hr; - STRU strTemp; - m_fKeepAlive = TRUE; m_msTimeout = 120000; m_fPreserveHostHeader = TRUE; m_fReverseRewriteHeaders = FALSE; - if (FAILED(hr = m_strXForwardedForName.CopyW(L"X-Forwarded-For"))) - { - goto Finished; - } - - if (FAILED(hr = m_strSslHeaderName.CopyW(L"X-Forwarded-Proto"))) - { - goto Finished; - } - - if (FAILED(hr = m_strClientCertName.CopyW(L"MS-ASPNETCORE-CLIENTCERT"))) - { - goto Finished; - } + RETURN_IF_FAILED(m_strXForwardedForName.CopyW(L"X-Forwarded-For")); + RETURN_IF_FAILED(m_strSslHeaderName.CopyW(L"X-Forwarded-Proto")); + RETURN_IF_FAILED(m_strClientCertName.CopyW(L"MS-ASPNETCORE-CLIENTCERT")); m_fIncludePortInXForwardedFor = TRUE; m_dwMinResponseBuffer = 0; // no response buffering m_dwResponseBufferLimit = 4096*1024; m_dwMaxResponseHeaderSize = 65536; - -Finished: - - return hr; + return S_OK; } VOID PROTOCOL_CONFIG::OverrideConfig( - ASPNETCORE_CONFIG *pAspNetCoreConfig + REQUESTHANDLER_CONFIG *pAspNetCoreConfig ) { m_msTimeout = pAspNetCoreConfig->QueryRequestTimeoutInMS(); -} \ No newline at end of file +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.h similarity index 97% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.h index 0bb34aa53d..a31ca1ee33 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/protocolconfig.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/protocolconfig.h @@ -16,7 +16,7 @@ class PROTOCOL_CONFIG VOID OverrideConfig( - ASPNETCORE_CONFIG *pAspNetCoreConfig + REQUESTHANDLER_CONFIG *pAspNetCoreConfig ); BOOL diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/resource.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/resource.h new file mode 100644 index 0000000000..4d54425300 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by HtmlResponses.rc +// +#define OUT_OF_PROCESS_RH_STATIC_HTML 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/responseheaderhash.cpp similarity index 91% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/responseheaderhash.cpp index f2fae274d5..55dcc2fbd4 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/responseheaderhash.cpp @@ -1,9 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "responseheaderhash.h" +#include "exceptions.h" -HEADER_RECORD RESPONSE_HEADER_HASH::sm_rgHeaders[] = +HEADER_RECORD RESPONSE_HEADER_HASH::sm_rgHeaders[] = { { "Cache-Control", HttpHeaderCacheControl }, { "Connection", HttpHeaderConnection }, @@ -65,8 +66,6 @@ Return Value: --*/ { - HRESULT hr; - // // 31 response headers. // Make sure to update the number of buckets it new headers @@ -79,20 +78,13 @@ Return Value: // Known collisions are "Age" colliding with "Expire" and "Location" // colliding with both "Expire" and "Age". // - hr = HASH_TABLE::Initialize(79); - if (FAILED(hr)) - { - return hr; - } + RETURN_IF_FAILED(HASH_TABLE::Initialize(79)); for ( DWORD Index = 0; Index < _countof(sm_rgHeaders); ++Index ) { - if (FAILED(hr = InsertRecord(&sm_rgHeaders[Index]))) - { - return hr; - } + RETURN_IF_FAILED(InsertRecord(&sm_rgHeaders[Index])); } - + return S_OK; } diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/responseheaderhash.h similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/responseheaderhash.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/responseheaderhash.h diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp similarity index 92% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp index de309bf643..494911472a 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/serverprocess.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/serverprocess.cpp @@ -1,8 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "serverprocess.h" + #include +#include "EventLog.h" +#include "file_utility.h" +#include "exceptions.h" //#include //extern BOOL g_fNsiApiNotSupported; @@ -22,32 +26,33 @@ SERVER_PROCESS::Initialize( BOOL fAnonymousAuthEnabled, ENVIRONMENT_VAR_HASH *pEnvironmentVariables, BOOL fStdoutLogEnabled, - BOOL fWebsocketsEnabled, + BOOL fWebSocketSupported, STRU *pstruStdoutLogFile, STRU *pszAppPhysicalPath, STRU *pszAppPath, STRU *pszAppVirtualPath ) { - HRESULT hr = S_OK; + HRESULT hr = S_OK; m_pProcessManager = pProcessManager; m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS; m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; m_fStdoutLogEnabled = fStdoutLogEnabled; + m_fWebSocketSupported = fWebSocketSupported; m_fWindowsAuthEnabled = fWindowsAuthEnabled; m_fBasicAuthEnabled = fBasicAuthEnabled; m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; - m_fWebsocketsEnabled = fWebsocketsEnabled; m_pProcessManager->ReferenceProcessManager(); + m_fDebuggerAttached = FALSE; - if (FAILED(hr = m_ProcessPath.Copy(*pszProcessExePath)) || - FAILED(hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| - FAILED(hr = m_struPhysicalPath.Copy(*pszAppPhysicalPath))|| - FAILED(hr = m_struAppFullPath.Copy(*pszAppPath))|| - FAILED(hr = m_struAppVirtualPath.Copy(*pszAppVirtualPath))|| - FAILED(hr = m_Arguments.Copy(*pszArguments)) || - FAILED(hr = SetupJobObject())) + if (FAILED_LOG(hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED_LOG(hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED_LOG(hr = m_struPhysicalPath.Copy(*pszAppPhysicalPath))|| + FAILED_LOG(hr = m_struAppFullPath.Copy(*pszAppPath))|| + FAILED_LOG(hr = m_struAppVirtualPath.Copy(*pszAppVirtualPath))|| + FAILED_LOG(hr = m_Arguments.Copy(*pszArguments)) || + FAILED_LOG(hr = SetupJobObject())) { goto Finished; } @@ -105,13 +110,15 @@ SERVER_PROCESS::GetRandomPort BOOL fPortInUse = FALSE; DWORD dwActualProcessId = 0; + std::uniform_int_distribution<> dist(MIN_PORT, MAX_PORT); + if (g_fNsiApiNotSupported) { // // the default value for optional parameter dwExcludedPort is 0 which is reserved // a random number between MIN_PORT and MAX_PORT // - while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + while ((*pdwPickedPort = dist(m_randomGenerator)) == dwExcludedPort); } else { @@ -123,7 +130,7 @@ SERVER_PROCESS::GetRandomPort // determing whether the randomly generated port is // in use by any other process. // - while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + while ((*pdwPickedPort = dist(m_randomGenerator)) == dwExcludedPort); hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse); } while (fPortInUse && ++cRetry < MAX_RETRY); @@ -149,7 +156,7 @@ SERVER_PROCESS::SetupListenPort( pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); if (pEntry != NULL) { - if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') + if (pEntry->QueryValue() != NULL && pEntry->QueryValue()[0] != L'\0') { m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); if (m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) @@ -168,13 +175,13 @@ SERVER_PROCESS::SetupListenPort( // user set the env variable but did not give value, let's set it up // pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR); + pEntry->Dereference(); + pEntry = NULL; } - pEntry->Dereference(); - pEntry = NULL; } WCHAR buffer[15]; - if (FAILED(hr = GetRandomPort(&m_dwPort))) + if (FAILED_LOG(hr = GetRandomPort(&m_dwPort))) { goto Finished; } @@ -192,9 +199,9 @@ SERVER_PROCESS::SetupListenPort( goto Finished; } - if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || - FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || - FAILED(hr = m_struPort.Copy(buffer))) + if (FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || + FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || + FAILED_LOG(hr = m_struPort.Copy(buffer))) { goto Finished; } @@ -206,10 +213,9 @@ Finished: pEntry = NULL; } - if (FAILED(hr)) + if (FAILED_LOG(hr)) { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, + EventLog::Error( ASPNETCORE_EVENT_PROCESS_START_SUCCESS, ASPNETCORE_EVENT_PROCESS_START_PORTSETUP_ERROR_MSG, m_struAppFullPath.QueryStr(), @@ -247,8 +253,8 @@ SERVER_PROCESS::SetupAppPath( goto Finished; } - if (FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppVirtualPath.QueryStr())) || - FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + if (FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppVirtualPath.QueryStr())) || + FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry))) { goto Finished; } @@ -306,7 +312,7 @@ SERVER_PROCESS::SetupAppToken( fRpcStringAllocd = TRUE; - if (FAILED(hr = m_straGuid.Copy(pszLogUuid))) + if (FAILED_LOG(hr = m_straGuid.Copy(pszLogUuid))) { goto Finished; } @@ -319,9 +325,9 @@ SERVER_PROCESS::SetupAppToken( goto Finished; } - if (FAILED(strAppToken.CopyA(m_straGuid.QueryStr())) || - FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) || - FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + if (FAILED_LOG(strAppToken.CopyA(m_straGuid.QueryStr())) || + FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) || + FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry))) { goto Finished; } @@ -377,7 +383,7 @@ SERVER_PROCESS::OutputEnvironmentVariables pszEqualChar = wcschr(pszCurrentVariable, L'='); if (pszEqualChar != NULL) { - if (FAILED(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1))) + if (FAILED_LOG(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1))) { goto Finished; } @@ -385,7 +391,7 @@ SERVER_PROCESS::OutputEnvironmentVariables if (pEntry != NULL) { // same env variable is defined in configuration, use it - if (FAILED(hr = strEnvVar.Append(pEntry->QueryValue()))) + if (FAILED_LOG(hr = strEnvVar.Append(pEntry->QueryValue()))) { goto Finished; } @@ -448,9 +454,9 @@ SERVER_PROCESS::SetupCommandLine( if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) { // let's check whether it is a relative path - if (FAILED(hr = strRelativePath.Copy(m_struPhysicalPath.QueryStr())) || - FAILED(hr = strRelativePath.Append(L"\\")) || - FAILED(hr = strRelativePath.Append(pszPath))) + if (FAILED_LOG(hr = strRelativePath.Copy(m_struPhysicalPath.QueryStr())) || + FAILED_LOG(hr = strRelativePath.Append(L"\\")) || + FAILED_LOG(hr = strRelativePath.Append(pszPath))) { goto Finished; } @@ -477,9 +483,9 @@ SERVER_PROCESS::SetupCommandLine( pszPath = pszFullPath; } } - if (FAILED(hr = pstrCommandLine->Copy(pszPath)) || - FAILED(hr = pstrCommandLine->Append(L" ")) || - FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) + if (FAILED_LOG(hr = pstrCommandLine->Copy(pszPath)) || + FAILED_LOG(hr = pstrCommandLine->Append(L" ")) || + FAILED_LOG(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) { goto Finished; } @@ -561,7 +567,7 @@ SERVER_PROCESS::PostStartCheck( } // register call back with the created process - if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) + if (FAILED_LOG(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) { goto Finished; } @@ -590,7 +596,7 @@ SERVER_PROCESS::PostStartCheck( if (!fProcessMatch) { // could be the scenario that backend creates child process - if (FAILED(hr = GetChildProcessHandles())) + if (FAILED_LOG(hr = GetChildProcessHandles())) { goto Finished; } @@ -612,7 +618,7 @@ SERVER_PROCESS::PostStartCheck( fDebuggerAttached = FALSE; } - if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + if (FAILED_LOG(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], m_hChildProcessHandles[i]))) { goto Finished; @@ -673,7 +679,7 @@ SERVER_PROCESS::PostStartCheck( hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); - if ((FAILED(hr) || fReady == FALSE)) + if ((FAILED_LOG(hr) || fReady == FALSE)) { strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, @@ -700,7 +706,7 @@ SERVER_PROCESS::PostStartCheck( } hr = m_pForwarderConnection->Initialize(m_dwPort); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -719,7 +725,9 @@ SERVER_PROCESS::PostStartCheck( m_fReady = TRUE; Finished: - if (FAILED(hr)) + m_fDebuggerAttached = fDebuggerAttached; + + if (FAILED_LOG(hr)) { if (m_pForwarderConnection != NULL) { @@ -729,9 +737,7 @@ Finished: if (!strEventMsg.IsEmpty()) { - UTILITY::LogEvent( - g_hEventLog, - EVENTLOG_WARNING_TYPE, + EventLog::Warn( ASPNETCORE_EVENT_PROCESS_START_ERROR, strEventMsg.QueryStr()); } @@ -768,13 +774,13 @@ SERVER_PROCESS::StartProcess( // // generate process command line. // - if (FAILED(hr = SetupCommandLine(&m_struCommandLine))) + if (FAILED_LOG(hr = SetupCommandLine(&m_struCommandLine))) { pStrStage = L"SetupCommandLine"; goto Failure; } - if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( + if (FAILED_LOG(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( m_pEnvironmentVarTable, m_fWindowsAuthEnabled, m_fBasicAuthEnabled, @@ -785,9 +791,9 @@ SERVER_PROCESS::StartProcess( goto Failure; } - if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables( + if (FAILED_LOG(hr = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables( pHashTable, - m_fWebsocketsEnabled + m_fWebSocketSupported ))) { pStrStage = L"AddWebsocketEnabledToEnvironmentVariables"; @@ -798,7 +804,7 @@ SERVER_PROCESS::StartProcess( // // setup the the port that the backend process will listen on // - if (FAILED(hr = SetupListenPort(pHashTable, &fCriticalError))) + if (FAILED_LOG(hr = SetupListenPort(pHashTable, &fCriticalError))) { pStrStage = L"SetupListenPort"; goto Failure; @@ -807,7 +813,7 @@ SERVER_PROCESS::StartProcess( // // get app path // - if (FAILED(hr = SetupAppPath(pHashTable))) + if (FAILED_LOG(hr = SetupAppPath(pHashTable))) { pStrStage = L"SetupAppPath"; goto Failure; @@ -816,7 +822,7 @@ SERVER_PROCESS::StartProcess( // // generate new guid for each process // - if (FAILED(hr = SetupAppToken(pHashTable))) + if (FAILED_LOG(hr = SetupAppToken(pHashTable))) { pStrStage = L"SetupAppToken"; goto Failure; @@ -825,7 +831,7 @@ SERVER_PROCESS::StartProcess( // // setup environment variables for new process // - if (FAILED(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable))) + if (FAILED_LOG(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable))) { pStrStage = L"OutputEnvironmentVariables"; goto Failure; @@ -856,7 +862,7 @@ SERVER_PROCESS::StartProcess( m_hProcessHandle = processInformation.hProcess; m_dwProcessId = processInformation.dwProcessId; - if (FAILED(hr = SetupJobObject())) + if (FAILED_LOG(hr = SetupJobObject())) { pStrStage = L"SetupJobObject"; goto Failure; @@ -885,7 +891,7 @@ SERVER_PROCESS::StartProcess( // // need to make sure the server is up and listening on the port specified. // - if (FAILED(hr = PostStartCheck())) + if (FAILED_LOG(hr = PostStartCheck())) { pStrStage = L"PostStartCheck"; goto Failure; @@ -894,8 +900,7 @@ SERVER_PROCESS::StartProcess( // Backend process starts successfully. Set retry counter to 0 dwRetryCount = 0; - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, + EventLog::Info( ASPNETCORE_EVENT_PROCESS_START_SUCCESS, ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, m_struAppFullPath.QueryStr(), @@ -912,8 +917,7 @@ SERVER_PROCESS::StartProcess( dwRetryCount = 0; } - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_WARNING_TYPE, + EventLog::Warn( ASPNETCORE_EVENT_PROCESS_START_ERROR, ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, m_struAppFullPath.QueryStr(), @@ -941,7 +945,7 @@ SERVER_PROCESS::StartProcess( } Finished: - if (FAILED(hr) || m_fReady == FALSE) + if (FAILED_LOG(hr) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) { @@ -957,8 +961,7 @@ Finished: m_Timer.CancelTimer(); } - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, + EventLog::Error( ASPNETCORE_EVENT_PROCESS_START_FAILURE, ASPNETCORE_EVENT_PROCESS_START_FAILURE_MSG, m_struAppFullPath.QueryStr(), @@ -1028,11 +1031,11 @@ SERVER_PROCESS::SetupStdHandles( m_hStdoutHandle = NULL; } - hr = UTILITY::ConvertPathToFullPath( + hr = FILE_UTILITY::ConvertPathToFullPath( m_struLogFile.QueryStr(), m_struPhysicalPath.QueryStr(), &struPath); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -1047,13 +1050,13 @@ SERVER_PROCESS::SetupStdHandles( systemTime.wMinute, systemTime.wSecond, GetCurrentProcessId()); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } - hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); - if (FAILED(hr)) + hr = FILE_UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); + if (FAILED_LOG(hr)) { goto Finished; } @@ -1084,7 +1087,7 @@ SERVER_PROCESS::SetupStdHandles( m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struFullLogFile, 3000, 3000); Finished: - if (FAILED(hr)) + if (FAILED_LOG(hr)) { pStartupInfo->dwFlags = STARTF_USESTDHANDLES; pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; @@ -1094,8 +1097,7 @@ Finished: if (m_fStdoutLogEnabled) { // Log the error - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_WARNING_TYPE, + EventLog::Warn( ASPNETCORE_EVENT_CONFIG_ERROR, ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, m_struFullLogFile.IsEmpty()? m_struLogFile.QueryStr() : m_struFullLogFile.QueryStr(), @@ -1277,7 +1279,12 @@ SERVER_PROCESS::SendSignal( goto Finished; } - if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) + // + // Reset the shutdown timeout if debugger is attached. + // Do it only for the case that debugger is attached during process creation + // as IsDebuggerIsAttached call is too heavy + // + if (WaitForSingleObject(m_hShutdownHandle, m_fDebuggerAttached ? INFINITE : m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) { hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); goto Finished; @@ -1299,7 +1306,7 @@ Finished: hThread = NULL; } - if (FAILED(hr)) + if (FAILED_LOG(hr)) { TerminateBackendProcess(); } @@ -1696,10 +1703,10 @@ SERVER_PROCESS::SERVER_PROCESS() : m_pForwarderConnection(NULL), m_dwListeningProcessId(0), m_hListeningProcessHandle(NULL), - m_hShutdownHandle(NULL) + m_hShutdownHandle(NULL), + m_randomGenerator(std::random_device()()) { //InterlockedIncrement(&g_dwActiveServerProcesses); - srand(GetTickCount()); for (INT i=0; i + #define MIN_PORT 1025 #define MAX_PORT 48000 #define MAX_RETRY 10 @@ -33,7 +35,7 @@ public: _In_ BOOL fAnonymousAuthEnabled, _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, _In_ BOOL fStdoutLogEnabled, - _In_ BOOL fWebsocketsEnabled, + _In_ BOOL fWebSocketSupported, _In_ STRU *pstruStdoutLogFile, _In_ STRU *pszAppPhysicalPath, _In_ STRU *pszAppPath, @@ -57,6 +59,14 @@ public: return m_fReady; } + BOOL + IsDebuggerAttached( + VOID + ) + { + return m_fDebuggerAttached; + } + VOID StopProcess( VOID @@ -226,10 +236,11 @@ private: FORWARDER_CONNECTION *m_pForwarderConnection; BOOL m_fStdoutLogEnabled; + BOOL m_fWebSocketSupported; BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; - BOOL m_fWebsocketsEnabled; + BOOL m_fDebuggerAttached; STTIMER m_Timer; SOCKET m_socket; @@ -248,6 +259,8 @@ private: volatile BOOL m_fReady; mutable LONG m_cRefs; + std::mt19937 m_randomGenerator; + DWORD m_dwPort; DWORD m_dwStartupTimeLimitInMS; DWORD m_dwShutdownTimeLimitInMS; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.cpp new file mode 100644 index 0000000000..12fcb1d436 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.cpp @@ -0,0 +1,4 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// Do not remove this file. It is used for precompiled header generation diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/precomp.hxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h similarity index 68% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/precomp.hxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h index 2fa43aac17..91b6b002dc 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/precomp.hxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h @@ -26,6 +26,7 @@ #include #include #include + // This should remove our issue of compiling for win7 without header files. // We force the Windows 8 version check logic in iiswebsocket.h to succeed even though we're compiling for Windows 7. // Then, we set the version defines back to Windows 7 to for the remainder of the compilation. @@ -44,34 +45,34 @@ #define WINVER 0x0601 #define _WIN32_WINNT 0x0601 -#include "..\IISLib\acache.h" -#include "..\IISLib\multisz.h" -#include "..\IISLib\multisza.h" -#include "..\IISLib\base64.h" -#include "..\IISLib\listentry.h" -#include "..\CommonLib\fx_ver.h" -#include "..\CommonLib\debugutil.h" -#include "..\CommonLib\requesthandler.h" -#include "..\CommonLib\aspnetcoreconfig.h" -#include "..\CommonLib\utility.h" -#include "..\CommonLib\application.h" -#include "..\CommonLib\resources.h" -#include "aspnetcore_event.h" +// IIS Lib +#include "acache.h" +#include "multisz.h" +#include "multisza.h" +#include "base64.h" +#include "listentry.h" +#include "debugutil.h" + +// Common lib +#include "requesthandler.h" +#include "application.h" +#include "resources.h" +#include "EventTracing.h" #include "aspnetcore_msg.h" -#include "disconnectcontext.h" -#include "environmentvariablehelpers.h" +#include "requesthandler_config.h" + #include "sttimer.h" -#include ".\inprocess\InProcessHandler.h" -#include ".\inprocess\inprocessapplication.h" -#include ".\outofprocess\responseheaderhash.h" -#include ".\outofprocess\protocolconfig.h" -#include ".\outofprocess\forwarderconnection.h" -#include ".\outofprocess\serverprocess.h" -#include ".\outofprocess\processmanager.h" -#include ".\outofprocess\websockethandler.h" -#include ".\outofprocess\forwardinghandler.h" -#include ".\outofprocess\outprocessapplication.h" -#include ".\outofprocess\winhttphelper.h" +#include "websockethandler.h" +#include "responseheaderhash.h" +#include "protocolconfig.h" +#include "forwarderconnection.h" +#include "serverprocess.h" +#include "processmanager.h" +#include "forwardinghandler.h" +#include "outprocessapplication.h" +#include "winhttphelper.h" + +#include "environmentvariablehelpers.h" #ifdef max #undef max @@ -89,7 +90,6 @@ template inline T min(T a, T b) } #endif - inline bool IsSpace(char ch) { switch (ch) @@ -108,7 +108,7 @@ inline bool IsSpace(char ch) extern BOOL g_fAsyncDisconnectAvailable; extern BOOL g_fWinHttpNonBlockingCallbackAvailable; -extern BOOL g_fWebSocketSupported; +extern BOOL g_fWebSocketStaticInitialize; extern BOOL g_fNsiApiNotSupported; extern BOOL g_fEnableReferenceCountTracing; extern BOOL g_fProcessDetach; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.cpp new file mode 100644 index 0000000000..64118b61b6 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.cpp @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "url_utility.h" + +#include +#include "debugutil.h" +#include "exceptions.h" + +// static +HRESULT +URL_UTILITY::SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl +) +/*++ + +Routine Description: + + Split the URL specified for forwarding into its specific components + The format of the URL looks like + http[s]://destination[:port]/path + when port is omitted, the default port for that specific protocol is used + when host is omitted, it gets the same value as the destination + +Arguments: + + pszDestinationUrl - the url to be split up + pfSecure - SSL to be used in forwarding? + pstrDestination - destination + pDestinationPort - port + pstrUrl - URL + +Return Value: + + HRESULT + +--*/ +{ + // + // First determine if the target is secure + // + if (_wcsnicmp(pszDestinationUrl, L"http://", 7) == 0) + { + *pfSecure = FALSE; + pszDestinationUrl += 7; + } + else if (_wcsnicmp(pszDestinationUrl, L"https://", 8) == 0) + { + *pfSecure = TRUE; + pszDestinationUrl += 8; + } + else + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); + } + + if (*pszDestinationUrl == L'\0') + { + RETURN_HR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA)); + } + + // + // Find the 3rd slash corresponding to the url + // + LPCWSTR pszSlash = wcschr(pszDestinationUrl, L'/'); + if (pszSlash == NULL) + { + RETURN_IF_FAILED(pstrUrl->Copy(L"/", 1)); + RETURN_IF_FAILED(pstrDestination->Copy(pszDestinationUrl)); + } + else + { + RETURN_IF_FAILED(pstrUrl->Copy(pszSlash)); + RETURN_IF_FAILED(pstrDestination->Copy(pszDestinationUrl, + (DWORD)(pszSlash - pszDestinationUrl))); + } + + return S_OK; +} + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + +HRESULT +URL_UTILITY::EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl +) +{ + STRU strAbsPath; + LPCWSTR pszAbsPath = NULL; + LPCWSTR pszFindStr = NULL; + + RETURN_IF_FAILED(strAbsPath.Copy( pRequest->GetRawHttpRequest()->CookedUrl.pAbsPath, + pRequest->GetRawHttpRequest()->CookedUrl.AbsPathLength / sizeof(WCHAR) )); + + pszAbsPath = strAbsPath.QueryStr(); + pszFindStr = wcschr(pszAbsPath, L'?'); + + while(pszFindStr != NULL) + { + RETURN_IF_FAILED(strEscapedUrl->Append( pszAbsPath, pszFindStr - pszAbsPath)); + RETURN_IF_FAILED(strEscapedUrl->Append(L"%3F")); + pszAbsPath = pszFindStr + 1; + pszFindStr = wcschr(pszAbsPath, L'?'); + } + + RETURN_IF_FAILED(strEscapedUrl->Append(pszAbsPath)); + RETURN_IF_FAILED(strEscapedUrl->Append(pRequest->GetRawHttpRequest()->CookedUrl.pQueryString, + pRequest->GetRawHttpRequest()->CookedUrl.QueryStringLength / sizeof(WCHAR))); + + return S_OK; +} + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.h new file mode 100644 index 0000000000..a096a96cf0 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/url_utility.h @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "stdafx.h" + +#include +#include "stringu.h" + +class URL_UTILITY +{ +public: + + static + HRESULT + SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl + ); + + static HRESULT + EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl + ); +}; + diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/websockethandler.cpp similarity index 84% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/websockethandler.cpp index ae7ecf697e..ecd97737fe 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/websockethandler.cpp @@ -27,7 +27,8 @@ This prevents the need for data buffering at the Asp.Net Core Module level. --*/ -#include "..\precomp.hxx" +#include "websockethandler.h" +#include "exceptions.h" SRWLOCK WEBSOCKET_HANDLER::sm_RequestsListLock; @@ -46,7 +47,7 @@ WEBSOCKET_HANDLER::WEBSOCKET_HANDLER() : _fHandleClosed(FALSE), _fReceivedCloseMsg(FALSE) { - DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); + LOG_TRACE(L"WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000); InsertRequest(); @@ -57,7 +58,7 @@ WEBSOCKET_HANDLER::Terminate( VOID ) { - DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate"); + LOG_TRACE(L"WEBSOCKET_HANDLER::Terminate"); if (!_fHandleClosed) { RemoveRequest(); @@ -86,7 +87,7 @@ HRESULT WEBSOCKET_HANDLER::StaticInitialize( BOOL fEnableReferenceCountTracing ) -/*++ +/*++ Routine Description: @@ -94,7 +95,7 @@ WEBSOCKET_HANDLER::StaticInitialize( --*/ { - if (!g_fWebSocketSupported) + if (!g_fWebSocketStaticInitialize) { return S_OK; } @@ -120,7 +121,7 @@ WEBSOCKET_HANDLER::StaticTerminate( VOID ) { - if (!g_fWebSocketSupported) + if (!g_fWebSocketStaticInitialize) { return; } @@ -145,7 +146,7 @@ WEBSOCKET_HANDLER::InsertRequest( } } -//static +//static VOID WEBSOCKET_HANDLER::RemoveRequest( VOID @@ -211,8 +212,7 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS( --*/ { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::IndicateCompletionToIIS called %d", _dwOutstandingIo); + LOG_TRACEF(L"WEBSOCKET_HANDLER::IndicateCompletionToIIS called %d", _dwOutstandingIo); // // close Websocket handle. This will triger a WinHttp callback @@ -222,8 +222,7 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS( // if (_hWebSocketRequest != NULL && _dwOutstandingIo == 0) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::IndicateCompletionToIIS"); + LOG_TRACE(L"WEBSOCKET_HANDLER::IndicateCompletionToIIS"); _pHandler->SetStatus(FORWARDER_DONE); _fHandleClosed = TRUE; @@ -244,11 +243,11 @@ WEBSOCKET_HANDLER::ProcessRequest( Routine Description: Entry point to WebSocket Handler: - + This routine is called after the 101 response was successfully sent to - the client. - This routine get's a websocket handle to winhttp, - websocket handle to IIS's websocket context, and initiates IO + the client. + This routine get's a websocket handle to winhttp, + websocket handle to IIS's websocket context, and initiates IO in these two endpoints. @@ -261,16 +260,15 @@ Routine Description: _pHandler = pHandler; EnterCriticalSection(&_RequestLock); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::ProcessRequest"); + LOG_TRACEF(L"WEBSOCKET_HANDLER::ProcessRequest"); // // Cache the points to IHttpContext3 // - hr = HttpGetExtendedInterface(g_pHttpServer, - pHttpContext, + hr = HttpGetExtendedInterface(g_pHttpServer, + pHttpContext, &_pHttpContext); - if (FAILED (hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -338,7 +336,7 @@ Routine Description: // Initiate Read on IIS // hr = DoIisWebSocketReceive(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -348,7 +346,7 @@ Routine Description: // hr = DoWinHttpWebSocketReceive(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -356,10 +354,9 @@ Routine Description: Finished: LeaveCriticalSection(&_RequestLock); - if (FAILED (hr)) + if (FAILED_LOG(hr)) { - DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, - "Process Request Failed with HR=%08x", hr); + LOG_ERRORF(L"Process Request Failed with HR=%08x", hr); } return hr; @@ -384,8 +381,7 @@ Routine Description: BOOL fFinalFragment; BOOL fClose; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoIisWebSocketReceive"); + LOG_TRACE(L"WEBSOCKET_HANDLER::DoIisWebSocketReceive"); IncrementOutstandingIo(); @@ -399,11 +395,10 @@ Routine Description: OnReadIoCompletion, this, NULL); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { DecrementOutstandingIo(); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); } return hr; @@ -425,8 +420,7 @@ Routine Description: HRESULT hr = S_OK; DWORD dwError = NO_ERROR; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive"); + LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive"); IncrementOutstandingIo(); @@ -441,8 +435,7 @@ Routine Description: { DecrementOutstandingIo(); hr = HRESULT_FROM_WIN32(dwError); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr); } return hr; @@ -466,8 +459,7 @@ Routine Description: BOOL fFinalFragment = FALSE; BOOL fClose = FALSE; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoIisWebSocketSend %d", eBufferType); + LOG_TRACEF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend %d", eBufferType); if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { @@ -498,7 +490,7 @@ Routine Description: // hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer, dwReceived); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -549,16 +541,15 @@ Routine Description: NULL); } - if (FAILED(hr)) + if (FAILED_LOG(hr)) { DecrementOutstandingIo(); } Finished: - if (FAILED(hr)) + if (FAILED_LOG(hr)) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); } return hr; @@ -580,8 +571,7 @@ Routine Description: DWORD dwError = NO_ERROR; HRESULT hr = S_OK; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType); + LOG_TRACEF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType); if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { @@ -592,10 +582,10 @@ Routine Description: // // Get Close status from IIS. // - hr = _pWebSocketContext->GetCloseStatus(&uStatus, + hr = _pWebSocketContext->GetCloseStatus(&uStatus, &pszReason); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -604,7 +594,7 @@ Routine Description: // Convert status to UTF8 // hr = strCloseReason.CopyWToUTF8Unescaped(pszReason); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } @@ -626,8 +616,7 @@ Routine Description: // Call will complete asynchronously, return. // ignore error. // - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); + LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); dwError = NO_ERROR; } @@ -638,8 +627,7 @@ Routine Description: // // Call completed synchronously. // - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); + LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); } } } @@ -663,10 +651,9 @@ Routine Description: } Finished: - if (FAILED(hr)) + if (FAILED_LOG(hr)) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr); } return hr; @@ -691,7 +678,7 @@ WEBSOCKET_HANDLER::OnReadIoCompletion( --*/ { - WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) pvCompletionContext; pHandler->OnIisReceiveComplete( @@ -721,7 +708,7 @@ WEBSOCKET_HANDLER::OnWriteIoCompletion( --*/ { - WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) pvCompletionContext; UNREFERENCED_PARAMETER(fUTF8Encoded); @@ -754,8 +741,7 @@ Routine Description: BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); + LOG_TRACE(L"WEBSOCKET_HANDLER::OnWinHttpSendComplete"); if (_fCleanupInProgress) { @@ -776,23 +762,22 @@ Routine Description: // hr = DoIisWebSocketReceive(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { goto Finished; } Finished: - if (fLocked) + if (fLocked) { LeaveCriticalSection(&_RequestLock); } - if (FAILED (hr)) + if (FAILED_LOG(hr)) { Cleanup (cleanupReason); - DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr); } // @@ -809,8 +794,7 @@ WEBSOCKET_HANDLER::OnWinHttpShutdownComplete( VOID ) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::OnWinHttpShutdownComplete --%p", _pHandler); + LOG_TRACEF(L"WEBSOCKET_HANDLER::OnWinHttpShutdownComplete --%p", _pHandler); DecrementOutstandingIo(); @@ -824,8 +808,7 @@ WEBSOCKET_HANDLER::OnWinHttpIoError( { HRESULT hr = HRESULT_FROM_WIN32(pCompletionStatus->AsyncResult.dwError); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d", + LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d", hr, pCompletionStatus->AsyncResult.dwResult); Cleanup(ServerDisconnect); @@ -848,7 +831,7 @@ Routine Description: Issue send on the Client(IIS) if the receive was successful. - If the receive completed with zero bytes, that + If the receive completed with zero bytes, that indicates that the server has disconnected the connection. Issue cleanup for the websocket handler. --*/ @@ -857,8 +840,7 @@ Routine Description: BOOL fLocked = FALSE; CleanupReason cleanupReason = CleanupReasonUnknown; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete --%p", _pHandler); + LOG_TRACEF(L"WEBSOCKET_HANDLER::OnWinHttpReceiveComplete --%p", _pHandler); if (_fCleanupInProgress) { @@ -877,23 +859,22 @@ Routine Description: pCompletionStatus->eBufferType ); - if (FAILED (hr)) + if (FAILED_LOG(hr)) { cleanupReason = ClientDisconnect; goto Finished; } Finished: - if (fLocked) + if (fLocked) { LeaveCriticalSection(&_RequestLock); } - if (FAILED (hr)) + if (FAILED_LOG(hr)) { Cleanup (cleanupReason); - DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr); } // @@ -917,7 +898,7 @@ Routine Description: Completion callback executed when a send completes from the client. - If send was successful,issue read on the + If send was successful,issue read on the server endpoint, to continue the readloop. --*/ @@ -928,9 +909,9 @@ Routine Description: UNREFERENCED_PARAMETER(cbIo); - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); + LOG_TRACE(L"WEBSOCKET_HANDLER::OnIisSendComplete"); - if (FAILED(hrCompletion)) + if (FAILED_LOG(hrCompletion)) { hr = hrCompletion; cleanupReason = ClientDisconnect; @@ -957,7 +938,7 @@ Routine Description: // Write Completed, initiate next read from backend server. // hr = DoWinHttpWebSocketReceive(); - if (FAILED(hr)) + if (FAILED_LOG(hr)) { cleanupReason = ServerDisconnect; goto Finished; @@ -969,12 +950,11 @@ Finished: { LeaveCriticalSection(&_RequestLock); } - if (FAILED (hr)) + if (FAILED_LOG(hr)) { Cleanup (cleanupReason); - DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); } // @@ -1013,10 +993,9 @@ Routine Description: CleanupReason cleanupReason = CleanupReasonUnknown; WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::OnIisReceiveComplete"); + LOG_TRACE(L"WEBSOCKET_HANDLER::OnIisReceiveComplete"); - if (FAILED(hrCompletion)) + if (FAILED_LOG(hrCompletion)) { cleanupReason = ClientDisconnect; hr = hrCompletion; @@ -1029,7 +1008,7 @@ Routine Description: } EnterCriticalSection(&_RequestLock); - + fLocked = TRUE; if (_fCleanupInProgress) { @@ -1049,7 +1028,7 @@ Routine Description: // hr = DoWinHttpWebSocketSend(cbIO, BufferType); - if (FAILED (hr)) + if (FAILED_LOG(hr)) { cleanupReason = ServerDisconnect; goto Finished; @@ -1060,12 +1039,11 @@ Finished: { LeaveCriticalSection(&_RequestLock); } - if (FAILED (hr)) + if (FAILED_LOG(hr)) { Cleanup (cleanupReason); - DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, - "WEBSOCKET_HANDLER::OnIisReceiveComplete failed with HR=%08x", hr); + LOG_ERRORF(L"WEBSOCKET_HANDLER::OnIisReceiveComplete failed with HR=%08x", hr); } // @@ -1096,8 +1074,7 @@ Arguments: --*/ { BOOL fLocked = FALSE; - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + LOG_TRACEF(L"WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); if (_fCleanupInProgress) { diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/websockethandler.h similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/websockethandler.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/websockethandler.h diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/winhttphelper.cpp similarity index 79% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/winhttphelper.cpp index ce4256a710..8f6c5d3720 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/winhttphelper.cpp @@ -1,7 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include "..\precomp.hxx" +#include "winhttphelper.h" +#include "exceptions.h" PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade; @@ -24,66 +25,38 @@ WINHTTP_HELPER::StaticInitialize( VOID ) { - HRESULT hr = S_OK; - - if (!g_fWebSocketSupported) + // + // Initialize the function pointers for WinHttp Websocket API's. + // + if (!g_fWebSocketStaticInitialize) { return S_OK; } - // - // Initialize the function pointers for WinHttp Websocket API's. - // - HMODULE hWinHttp = GetModuleHandleA("winhttp.dll"); - if (hWinHttp == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (hWinHttp == NULL); - sm_pfnWinHttpWebSocketCompleteUpgrade = (PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE) + sm_pfnWinHttpWebSocketCompleteUpgrade = (PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE) GetProcAddress(hWinHttp, "WinHttpWebSocketCompleteUpgrade"); - if (sm_pfnWinHttpWebSocketCompleteUpgrade == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (sm_pfnWinHttpWebSocketCompleteUpgrade == NULL); sm_pfnWinHttpWebSocketQueryCloseStatus = (PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS) GetProcAddress(hWinHttp, "WinHttpWebSocketQueryCloseStatus"); - if (sm_pfnWinHttpWebSocketQueryCloseStatus == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (sm_pfnWinHttpWebSocketQueryCloseStatus == NULL); sm_pfnWinHttpWebSocketReceive = (PFN_WINHTTP_WEBSOCKET_RECEIVE) GetProcAddress(hWinHttp, "WinHttpWebSocketReceive"); - if (sm_pfnWinHttpWebSocketReceive == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (sm_pfnWinHttpWebSocketReceive == NULL); sm_pfnWinHttpWebSocketSend = (PFN_WINHTTP_WEBSOCKET_SEND) GetProcAddress(hWinHttp, "WinHttpWebSocketSend"); - if (sm_pfnWinHttpWebSocketSend == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (sm_pfnWinHttpWebSocketSend == NULL); sm_pfnWinHttpWebSocketShutdown = (PFN_WINHTTP_WEBSOCKET_SHUTDOWN) GetProcAddress(hWinHttp, "WinHttpWebSocketShutdown"); - if (sm_pfnWinHttpWebSocketShutdown == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } + RETURN_LAST_ERROR_IF (sm_pfnWinHttpWebSocketShutdown == NULL); -Finished: - return hr; + return S_OK; } @@ -171,6 +144,4 @@ WINHTTP_HELPER::GetBufferTypeFromFlags( *pBufferType = WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE; } } - - return; -} \ No newline at end of file +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.h b/src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/winhttphelper.h similarity index 100% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/winhttphelper.h rename to src/IISIntegration/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/winhttphelper.h diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/Source.def b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/Source.def deleted file mode 100644 index 889bd1a39b..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/Source.def +++ /dev/null @@ -1,6 +0,0 @@ -LIBRARY aspnetcorerh - -EXPORTS - CreateApplication - CreateRequestHandler - diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/aspnetcore_event.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/aspnetcore_event.h deleted file mode 100644 index 2c13d20d1e..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/aspnetcore_event.h +++ /dev/null @@ -1,550 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#ifndef __ASPNETCOREEVENT_H__ -#define __ASPNETCOREEVENT_H__ -/*++ - - Module Name: - - aspnetcore_event.h - - Abstract: - - Header file has been generated from mof file containing - IIS trace event descriptions - ---*/ - -// -// Start of the new provider class WWWServerTraceProvider, -// GUID: {3a2a4e84-4c21-4981-ae10-3fda0d9b0f83} -// Description: IIS: WWW Server -// - -class WWWServerTraceProvider -{ -public: - static - LPCGUID - GetProviderGuid( VOID ) - // return GUID for the current event class - { - static const GUID ProviderGuid = - {0x3a2a4e84,0x4c21,0x4981,{0xae,0x10,0x3f,0xda,0x0d,0x9b,0x0f,0x83}}; - return &ProviderGuid; - }; - enum enumAreaFlags - { - // AspNetCore module events - ANCM = 0x10000 - }; - static - LPCWSTR - TranslateEnumAreaFlagsToString( enum enumAreaFlags EnumValue) - { - switch( (DWORD) EnumValue ) - { - case 0x10000: return L"ANCM"; - } - return NULL; - }; - - static - BOOL - CheckTracingEnabled( - IHttpTraceContext * pHttpTraceContext, - enumAreaFlags AreaFlags, - DWORD dwVerbosity ) - { - HRESULT hr; - HTTP_TRACE_CONFIGURATION TraceConfig; - TraceConfig.pProviderGuid = GetProviderGuid(); - hr = pHttpTraceContext->GetTraceConfiguration( &TraceConfig ); - if ( FAILED( hr ) || !TraceConfig.fProviderEnabled ) - { - return FALSE; - } - if ( TraceConfig.dwVerbosity >= dwVerbosity && - ( TraceConfig.dwAreas == (DWORD) AreaFlags || - ( TraceConfig.dwAreas & (DWORD)AreaFlags ) == (DWORD)AreaFlags ) ) - { - return TRUE; - } - return FALSE; - }; -}; - -// -// Start of the new event class ANCMEvents, -// GUID: {82ADEAD7-12B2-4781-BDCA-5A4B6C757191} -// Description: ANCM runtime events -// - -class ANCMEvents -{ -public: - static - LPCGUID - GetAreaGuid( VOID ) - // return GUID for the current event class - { - static const GUID AreaGuid = - {0x82adead7,0x12b2,0x4781,{0xbd,0xca,0x5a,0x4b,0x6c,0x75,0x71,0x91}}; - return &AreaGuid; - }; - - // - // Event: mof class name ANCMAppStart, - // Description: Start application success - // EventTypeName: ANCM_START_APPLICATION_SUCCESS - // EventType: 1 - // EventLevel: 4 - // - - class ANCM_START_APPLICATION_SUCCESS - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId, - LPCWSTR pAppDescription - ) - // - // Raise ANCM_START_APPLICATION_SUCCESS Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 1; - Event.pszEventName = L"ANCM_START_APPLICATION_SUCCESS"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 4; - Event.cEventItems = 2; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 2 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Items[ 1 ].pszName = L"AppDescription"; - Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) - Items[ 1 ].pbData = (PBYTE) pAppDescription; - Items[ 1 ].cbData = - ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); - Items[ 1 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 4 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMAppStartFail, - // Description: Start application failed - // EventTypeName: ANCM_START_APPLICATION_FAIL - // EventType: 2 - // EventLevel: 2 - // - - class ANCM_START_APPLICATION_FAIL - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId, - LPCWSTR pFailureDescription - ) - // - // Raise ANCM_START_APPLICATION_FAIL Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 2; - Event.pszEventName = L"ANCM_START_APPLICATION_FAIL"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 2; - Event.cEventItems = 2; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 2 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Items[ 1 ].pszName = L"FailureDescription"; - Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_LPCWSTR; // mof type (string) - Items[ 1 ].pbData = (PBYTE) pFailureDescription; - Items[ 1 ].cbData = - ( Items[ 1 ].pbData == NULL )? 0 : ( sizeof(WCHAR) * (1 + (DWORD) wcslen( (PWSTR) Items[ 1 ].pbData ) ) ); - Items[ 1 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 2 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMForwardStart, - // Description: Start forwarding request - // EventTypeName: ANCM_REQUEST_FORWARD_START - // EventType: 3 - // EventLevel: 4 - // - - class ANCM_REQUEST_FORWARD_START - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId - ) - // - // Raise ANCM_REQUEST_FORWARD_START Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 3; - Event.pszEventName = L"ANCM_REQUEST_FORWARD_START"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 4; - Event.cEventItems = 1; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 1 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 4 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMForwardEnd, - // Description: Finish forwarding request - // EventTypeName: ANCM_REQUEST_FORWARD_END - // EventType: 4 - // EventLevel: 4 - // - - class ANCM_REQUEST_FORWARD_END - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId - ) - // - // Raise ANCM_REQUEST_FORWARD_END Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 4; - Event.pszEventName = L"ANCM_REQUEST_FORWARD_END"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 4; - Event.cEventItems = 1; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 1 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 4 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMForwardFail, - // Description: Forwarding request failure - // EventTypeName: ANCM_REQUEST_FORWARD_FAIL - // EventType: 5 - // EventLevel: 2 - // - - class ANCM_REQUEST_FORWARD_FAIL - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId, - ULONG ErrorCode - ) - // - // Raise ANCM_REQUEST_FORWARD_FAIL Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 5; - Event.pszEventName = L"ANCM_REQUEST_FORWARD_FAIL"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 2; - Event.cEventItems = 2; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 2 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Items[ 1 ].pszName = L"ErrorCode"; - Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) - Items[ 1 ].pbData = (PBYTE) &ErrorCode; - Items[ 1 ].cbData = 4; - Items[ 1 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 2 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMWinHttpCallBack, - // Description: Receiving callback from WinHttp - // EventTypeName: ANCM_WINHTTP_CALLBACK - // EventType: 6 - // EventLevel: 4 - // - - class ANCM_WINHTTP_CALLBACK - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId, - ULONG InternetStatus - ) - // - // Raise ANCM_WINHTTP_CALLBACK Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 6; - Event.pszEventName = L"ANCM_WINHTTP_CALLBACK"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 4; - Event.cEventItems = 2; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 2 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Items[ 1 ].pszName = L"InternetStatus"; - Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) - Items[ 1 ].pbData = (PBYTE) &InternetStatus; - Items[ 1 ].cbData = 4; - Items[ 1 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 4 ); //Verbosity - }; - }; - // - // Event: mof class name ANCMForwardEnd, - // Description: Inprocess executing request failure - // EventTypeName: ANCM_EXECUTE_REQUEST_FAIL - // EventType: 7 - // EventLevel: 2 - // - - class ANCM_EXECUTE_REQUEST_FAIL - { - public: - static - HRESULT - RaiseEvent( - IHttpTraceContext * pHttpTraceContext, - LPCGUID pContextId, - ULONG ErrorCode - ) - // - // Raise ANCM_EXECUTE_REQUEST_FAIL Event - // - { - HTTP_TRACE_EVENT Event; - Event.pProviderGuid = WWWServerTraceProvider::GetProviderGuid(); - Event.dwArea = WWWServerTraceProvider::ANCM; - Event.pAreaGuid = ANCMEvents::GetAreaGuid(); - Event.dwEvent = 7; - Event.pszEventName = L"ANCM_EXECUTE_REQUEST_FAIL"; - Event.dwEventVersion = 1; - Event.dwVerbosity = 2; - Event.cEventItems = 2; - Event.pActivityGuid = NULL; - Event.pRelatedActivityGuid = NULL; - Event.dwTimeStamp = 0; - Event.dwFlags = HTTP_TRACE_EVENT_FLAG_STATIC_DESCRIPTIVE_FIELDS; - - // pActivityGuid, pRelatedActivityGuid, Timestamp to be filled in by IIS - - HTTP_TRACE_EVENT_ITEM Items[ 2 ]; - Items[ 0 ].pszName = L"ContextId"; - Items[ 0 ].dwDataType = HTTP_TRACE_TYPE_LPCGUID; // mof type (object) - Items[ 0 ].pbData = (PBYTE) pContextId; - Items[ 0 ].cbData = 16; - Items[ 0 ].pszDataDescription = NULL; - Items[ 1 ].pszName = L"ErrorCode"; - Items[ 1 ].dwDataType = HTTP_TRACE_TYPE_ULONG; // mof type (uint32) - Items[ 1 ].pbData = (PBYTE) &ErrorCode; - Items[ 1 ].cbData = 4; - Items[ 1 ].pszDataDescription = NULL; - Event.pEventItems = Items; - pHttpTraceContext->RaiseTraceEvent( &Event ); - return S_OK; - }; - - static - BOOL - IsEnabled( - IHttpTraceContext * pHttpTraceContext ) - // Check if tracing for this event is enabled - { - return WWWServerTraceProvider::CheckTracingEnabled( - pHttpTraceContext, - WWWServerTraceProvider::ANCM, - 2 ); //Verbosity - }; - }; -}; -#endif diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/disconnectcontext.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/disconnectcontext.h deleted file mode 100644 index e43a49c0a0..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/disconnectcontext.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext -{ -public: - ASYNC_DISCONNECT_CONTEXT() - { - m_pHandler = NULL; - } - - VOID - CleanupStoredContext() - { - DBG_ASSERT(m_pHandler == NULL); - delete this; - } - - VOID - NotifyDisconnect() - { - REQUEST_HANDLER *pInitialValue = (REQUEST_HANDLER*) - InterlockedExchangePointer((PVOID*)&m_pHandler, NULL); - - if (pInitialValue != NULL) - { - pInitialValue->TerminateRequest(TRUE); - pInitialValue->DereferenceRequestHandler(); - } - } - - VOID - SetHandler( - REQUEST_HANDLER *pHandler - ) - { - // - // Take a reference on the forwarding handler. - // This reference will be released on either of two conditions: - // - // 1. When the request processing ends, in which case a ResetHandler() - // is called. - // - // 2. When a disconnect notification arrives. - // - // We need to make sure that only one of them ends up dereferencing - // the object. - // - - DBG_ASSERT(pHandler != NULL); - DBG_ASSERT(m_pHandler == NULL); - - pHandler->ReferenceRequestHandler(); - InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler); - } - - VOID - ResetHandler( - VOID - ) - { - REQUEST_HANDLER *pInitialValue = (REQUEST_HANDLER*) - InterlockedExchangePointer((PVOID*)&m_pHandler, NULL); - - if (pInitialValue != NULL) - { - pInitialValue->DereferenceRequestHandler(); - } - } - -private: - ~ASYNC_DISCONNECT_CONTEXT() - {} - - REQUEST_HANDLER * m_pHandler; -}; \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.cpp deleted file mode 100644 index d8566262e1..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.cpp +++ /dev/null @@ -1,986 +0,0 @@ -#include "..\precomp.hxx" - -IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; - -IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( - IHttpServer* pHttpServer, - ASPNETCORE_CONFIG* pConfig) : - APPLICATION(pHttpServer, pConfig), - m_pHttpServer(pHttpServer), - m_ProcessExitCode(0), - m_hLogFileHandle(INVALID_HANDLE_VALUE), - m_hErrReadPipe(INVALID_HANDLE_VALUE), - m_hErrWritePipe(INVALID_HANDLE_VALUE), - m_dwStdErrReadTotal(0), - m_fDoneStdRedirect(FALSE), - m_fBlockCallbacksIntoManaged(FALSE), - m_fInitialized(FALSE), - m_fShutdownCalledFromNative(FALSE), - m_fShutdownCalledFromManaged(FALSE), - m_srwLock() -{ - // is it guaranteed that we have already checked app offline at this point? - // If so, I don't think there is much to do here. - DBG_ASSERT(pHttpServer != NULL); - DBG_ASSERT(pConfig != NULL); - InitializeSRWLock(&m_srwLock); - - // TODO we can probably initialized as I believe we are the only ones calling recycle. - m_status = APPLICATION_STATUS::STARTING; -} - -IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION() -{ - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - m_Timer.CancelTimer(); - CloseHandle(m_hLogFileHandle); - m_hLogFileHandle = INVALID_HANDLE_VALUE; - } - - m_hThread = NULL; - s_Application = NULL; -} - -//static -VOID -IN_PROCESS_APPLICATION::DoShutDown( - LPVOID lpParam -) -{ - IN_PROCESS_APPLICATION* pApplication = static_cast(lpParam); - DBG_ASSERT(pApplication); - pApplication->ShutDownInternal(); -} - -__override -VOID -IN_PROCESS_APPLICATION::ShutDown( - VOID -) -{ - HRESULT hr = S_OK; - CHandle hThread; - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); - - if (IsDebuggerPresent()) - { - dwTimeout = INFINITE; - } - - hThread.Attach(CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)DoShutDown, - this, // thread function arguments - 0, // default creation flags - NULL)); // receive thread identifier - - if ((HANDLE)hThread == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (WaitForSingleObject(hThread, dwTimeout) != WAIT_OBJECT_0) - { - // if the thread is still running, we need kill it first before exit to avoid AV - if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) - { - // Calling back into managed at this point is prone to have AVs - // Calling terminate thread here may be our best solution. - TerminateThread(hThread, STATUS_CONTROL_C_EXIT); - hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); - } - } - -Finished: - - if (FAILED(hr)) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_WARNING_TYPE, - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, - ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG, - m_pConfig->QueryConfigPath()->QueryStr()); - - // - // Managed layer may block the shutdown and lead to shutdown timeout - // Assumption: only one inprocess application is hosted. - // Call process exit to force shutdown - // - exit(hr); - } -} - - -VOID -IN_PROCESS_APPLICATION::ShutDownInternal() -{ - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); - HANDLE handle = NULL; - WIN32_FIND_DATA fileData; - - if (IsDebuggerPresent()) - { - dwTimeout = INFINITE; - } - - if (m_fShutdownCalledFromNative || - m_status == APPLICATION_STATUS::STARTING || - m_status == APPLICATION_STATUS::FAIL) - { - return; - } - - { - SRWLockWrapper lock(m_srwLock); - - if (m_fShutdownCalledFromNative || - m_status == APPLICATION_STATUS::STARTING || - m_status == APPLICATION_STATUS::FAIL) - { - return; - } - - // We need to keep track of when both managed and native initiate shutdown - // to avoid AVs. If shutdown has already been initiated in managed, we don't want to call into - // managed. We still need to wait on main exiting no matter what. m_fShutdownCalledFromNative - // is used for detecting redundant calls and blocking more requests to OnExecuteRequestHandler. - m_fShutdownCalledFromNative = TRUE; - m_status = APPLICATION_STATUS::SHUTDOWN; - - if (!m_fShutdownCalledFromManaged) - { - // We cannot call into managed if the dll is detaching from the process. - // Calling into managed code when the dll is detaching is strictly a bad idea, - // and usually results in an AV saying "The string binding is invalid" - if (!g_fProcessDetach) - { - m_ShutdownHandler(m_ShutdownHandlerContext); - m_ShutdownHandler = NULL; - } - } - - // Release the lock before we wait on the thread to exit. - } - - if (!m_fShutdownCalledFromManaged) - { - if (m_hThread != NULL && - GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && - dwThreadStatus == STILL_ACTIVE) - { - // wait for graceful shutdown, i.e., the exit of the background thread or timeout - if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0) - { - // if the thread is still running, we need kill it first before exit to avoid AV - if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) - { - // Calling back into managed at this point is prone to have AVs - // Calling terminate thread here may be our best solution. - TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT); - } - } - } - } - - CloseHandle(m_hThread); - m_hThread = NULL; - s_Application = NULL; - - CloseStdErrHandles(); - - if (m_pStdFile != NULL) - { - fflush(stdout); - fflush(stderr); - fclose(m_pStdFile); - } - - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - m_Timer.CancelTimer(); - CloseHandle(m_hLogFileHandle); - m_hLogFileHandle = INVALID_HANDLE_VALUE; - } - - // delete empty log file - handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData); - if (handle != INVALID_HANDLE_VALUE && - fileData.nFileSizeHigh == 0 && - fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh - { - FindClose(handle); - // no need to check whether the deletion succeeds - // as nothing can be done - DeleteFile(m_struLogFilePath.QueryStr()); - } -} - -__override -VOID -IN_PROCESS_APPLICATION::Recycle( - VOID -) -{ - // We need to guarantee that recycle is only called once, as calling pHttpServer->RecycleProcess - // multiple times can lead to AVs. - if (m_fRecycleCalled) - { - return; - } - - { - SRWLockWrapper lock(m_srwLock); - - if (m_fRecycleCalled) - { - return; - } - - m_fRecycleCalled = true; - } - - if (!m_pHttpServer->IsCommandLineLaunch()) - { - // IIS scenario. - // notify IIS first so that new request will be routed to new worker process - m_pHttpServer->RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); - } - else - { - // IISExpress scenario - // Shutdown the managed application and call exit to terminate current process - ShutDown(); - exit(0); - } -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus, - IN_PROCESS_HANDLER* pInProcessHandler -) -{ - REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; - - ReferenceApplication(); - - if (pInProcessHandler->QueryIsManagedRequestComplete()) - { - // means PostCompletion has been called and this is the associated callback. - dwRequestNotificationStatus = pInProcessHandler->QueryAsyncCompletionStatus(); - } - else if (m_fBlockCallbacksIntoManaged) - { - // this can potentially happen in ungraceful shutdown. - // Or something really wrong happening with async completions - // At this point, managed is in a shutting down state and we cannot send a request to it. - pInProcessHandler->QueryHttpContext()->GetResponse()->SetStatus(503, - "Server has been shutdown", - 0, - (ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else - { - // Call the managed handler for async completion. - dwRequestNotificationStatus = m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion); - } - - DereferenceApplication(); - - return dwRequestNotificationStatus; -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_APPLICATION::OnExecuteRequest( - _In_ IHttpContext* pHttpContext, - _In_ IN_PROCESS_HANDLER* pInProcessHandler -) -{ - REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE; - PFN_REQUEST_HANDLER pRequestHandler = NULL; - - ReferenceApplication(); - pRequestHandler = m_RequestHandler; - - if (pRequestHandler == NULL) - { - // - // return error as the application did not register callback - // - if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext())) - { - ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(), - NULL, - (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); - } - - pHttpContext->GetResponse()->SetStatus(500, - "Internal Server Error", - 0, - (ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE); - - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else if (m_status != APPLICATION_STATUS::RUNNING || m_fBlockCallbacksIntoManaged) - { - pHttpContext->GetResponse()->SetStatus(503, - "Server is currently shutting down.", - 0, - (ULONG)HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); - dwRequestNotificationStatus = RQ_NOTIFICATION_FINISH_REQUEST; - } - else - { - dwRequestNotificationStatus = pRequestHandler(pInProcessHandler, m_RequestHandlerContext); - } - - DereferenceApplication(); - - return dwRequestNotificationStatus; -} - -VOID -IN_PROCESS_APPLICATION::SetCallbackHandles( - _In_ PFN_REQUEST_HANDLER request_handler, - _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, - _In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler, - _In_ VOID* pvRequstHandlerContext, - _In_ VOID* pvShutdownHandlerContext -) -{ - m_RequestHandler = request_handler; - m_RequestHandlerContext = pvRequstHandlerContext; - m_ShutdownHandler = shutdown_handler; - m_ShutdownHandlerContext = pvShutdownHandlerContext; - m_AsyncCompletionHandler = async_completion_handler; - - CloseStdErrHandles(); - // Can't check the std err handle as it isn't a critical error - SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); - // Initialization complete - SetEvent(m_pInitalizeEvent); - m_fInitialized = TRUE; -} - -VOID -IN_PROCESS_APPLICATION::SetStdOut( - VOID -) -{ - HRESULT hr = S_OK; - STRU struPath; - SYSTEMTIME systemTime; - SECURITY_ATTRIBUTES saAttr = { 0 }; - HANDLE hStdErrReadPipe; - HANDLE hStdErrWritePipe; - - if (!m_fDoneStdRedirect) - { - // Have not set stdout yet, redirect stdout to log file - SRWLockWrapper lock(m_srwLock); - if (!m_fDoneStdRedirect) - { - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - // - // best effort - // no need to capture the error code as nothing we can do here - // in case mamanged layer exits abnormally, may not be able to capture the log content as it is buffered. - // - if (!GetConsoleWindow()) - { - // Full IIS scenario. - - // - // SetStdHandle works as w3wp does not have Console - // Current process does not have a console - // - if (m_pConfig->QueryStdoutLogEnabled()) - { - hr = UTILITY::ConvertPathToFullPath( - m_pConfig->QueryStdoutLogFile()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - &struPath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr()); - if (FAILED(hr)) - { - goto Finished; - } - - GetSystemTime(&systemTime); - hr = m_struLogFilePath.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log", - struPath.QueryStr(), - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond, - GetCurrentProcessId()); - if (FAILED(hr)) - { - goto Finished; - } - - m_hLogFileHandle = CreateFileW(m_struLogFilePath.QueryStr(), - FILE_READ_DATA | FILE_WRITE_DATA, - FILE_SHARE_READ, - &saAttr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (m_hLogFileHandle == INVALID_HANDLE_VALUE) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (!SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - if (!SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle)) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - // not work - // AllocConsole() does not help - // *stdout = *m_pStdFile; - // *stderr = *m_pStdFile; - // _dup2(_fileno(m_pStdFile), _fileno(stdout)); - // _dup2(_fileno(m_pStdFile), _fileno(stderr)); - // this one cannot capture the process start failure - // _wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); - - // Periodically flush the log content to file - m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struLogFilePath, 3000, 3000); - } - else - { - // - // CreatePipe for outputting stderr to the windows event log. - // Ignore failures - // - if (!CreatePipe(&hStdErrReadPipe, &hStdErrWritePipe, &saAttr, 0 /*nSize*/)) - { - goto Finished; - } - - if (!SetStdHandle(STD_ERROR_HANDLE, hStdErrWritePipe)) - { - goto Finished; - } - - m_hErrReadPipe = hStdErrReadPipe; - m_hErrWritePipe = hStdErrWritePipe; - - // Read the stderr handle on a separate thread until we get 4096 bytes. - m_hErrThread = CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)ReadStdErrHandle, - this, // thread function arguments - 0, // default creation flags - NULL); // receive thread identifier - - if (m_hErrThread == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - } - } - else - { - // The process has console, e.g., IIS Express scenario - - if (_wfopen_s(&m_pStdFile, m_struLogFilePath.QueryStr(), L"w") == 0) - { - // known issue: error info may not be capture when process crashes during buffering - // even we disabled FILE buffering - setvbuf(m_pStdFile, NULL, _IONBF, 0); - _dup2(_fileno(m_pStdFile), _fileno(stdout)); - _dup2(_fileno(m_pStdFile), _fileno(stderr)); - } - // These don't work for console scenario - // close and AllocConsole does not help - //_wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout); - // SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle); - // SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle); - //*stdout = *m_pStdFile; - //*stderr = *m_pStdFile; - } - } - } - -Finished: - m_fDoneStdRedirect = TRUE; - if (FAILED(hr) && m_pConfig->QueryStdoutLogEnabled()) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_WARNING_TYPE, - ASPNETCORE_EVENT_CONFIG_ERROR, - ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, - m_struLogFilePath.QueryStr(), - hr); - } -} - -VOID -IN_PROCESS_APPLICATION::ReadStdErrHandle( - LPVOID pContext -) -{ - IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext; - DBG_ASSERT(pApplication != NULL); - pApplication->ReadStdErrHandleInternal(); -} - -VOID -IN_PROCESS_APPLICATION::ReadStdErrHandleInternal( - VOID -) -{ - DWORD dwNumBytesRead = 0; - while (true) - { - if (ReadFile(m_hErrReadPipe, &m_pzFileContents[m_dwStdErrReadTotal], 4096 - m_dwStdErrReadTotal, &dwNumBytesRead, NULL)) - { - m_dwStdErrReadTotal += dwNumBytesRead; - if (m_dwStdErrReadTotal >= 4096) - { - break; - } - } - else if (GetLastError() == ERROR_BROKEN_PIPE) - { - break; - } - } -} - -VOID -IN_PROCESS_APPLICATION::CloseStdErrHandles -( - VOID -) -{ - DWORD dwThreadStatus = 0; - DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS(); - // Close Handles for stderr as we only care about capturing startup errors - if (m_hErrWritePipe != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hErrWritePipe); - m_hErrWritePipe = INVALID_HANDLE_VALUE; - } - - if (m_hErrThread != NULL && - GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && - dwThreadStatus == STILL_ACTIVE) - { - // wait for gracefullshut down, i.e., the exit of the background thread or timeout - if (WaitForSingleObject(m_hErrThread, dwTimeout) != WAIT_OBJECT_0) - { - // if the thread is still running, we need kill it first before exit to avoid AV - if (GetExitCodeThread(m_hErrThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE) - { - TerminateThread(m_hErrThread, STATUS_CONTROL_C_EXIT); - } - } - } - - CloseHandle(m_hErrThread); - m_hErrThread = NULL; - - if (m_hErrReadPipe != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hErrReadPipe); - m_hErrReadPipe = INVALID_HANDLE_VALUE; - } -} - -// Will be called by the inprocesshandler -HRESULT -IN_PROCESS_APPLICATION::LoadManagedApplication -( - VOID -) -{ - HRESULT hr = S_OK; - DWORD dwTimeout; - DWORD dwResult; - - ReferenceApplication(); - - if (m_status != APPLICATION_STATUS::STARTING) - { - // Core CLR has already been loaded. - // Cannot load more than once even there was a failure - if (m_status == APPLICATION_STATUS::FAIL) - { - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - } - else if (m_status == APPLICATION_STATUS::SHUTDOWN) - { - hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED); - } - - goto Finished; - } - - // Set up stdout redirect - SetStdOut(); - - { - SRWLockWrapper lock(m_srwLock); - if (m_status != APPLICATION_STATUS::STARTING) - { - if (m_status == APPLICATION_STATUS::FAIL) - { - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - } - else if (m_status == APPLICATION_STATUS::SHUTDOWN) - { - hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IS_SCHEDULED); - } - - goto Finished; - } - m_hThread = CreateThread( - NULL, // default security attributes - 0, // default stack size - (LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess, - this, // thread function arguments - 0, // default creation flags - NULL); // receive thread identifier - - if (m_hThread == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - m_pInitalizeEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual reset event - FALSE, // not set - NULL); // name - - if (m_pInitalizeEvent == NULL) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - - // If the debugger is attached, never timeout - if (IsDebuggerPresent()) - { - dwTimeout = INFINITE; - } - else - { - dwTimeout = m_pConfig->QueryStartupTimeLimitInMS(); - } - - const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent }; - - // Wait on either the thread to complete or the event to be set - dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout); - - // It all timed out - if (dwResult == WAIT_TIMEOUT) - { - // kill the backend thread as loading dotnet timedout - TerminateThread(m_hThread, 0); - hr = HRESULT_FROM_WIN32(dwResult); - goto Finished; - } - else if (dwResult == WAIT_FAILED) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - - // The thread ended it means that something failed - if (dwResult == WAIT_OBJECT_0) - { - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - goto Finished; - } - - m_status = APPLICATION_STATUS::RUNNING; - } -Finished: - - if (FAILED(hr)) - { - m_status = APPLICATION_STATUS::FAIL; - - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_LOAD_CLR_FALIURE, - ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr); - } - DereferenceApplication(); - - return hr; -} - -// static -VOID -IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess( - _In_ LPVOID pContext -) -{ - HRESULT hr = S_OK; - IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext; - DBG_ASSERT(pApplication != NULL); - hr = pApplication->ExecuteApplication(); - // - // no need to log the error here as if error happened, the thread will exit - // the error will ba catched by caller LoadManagedApplication which will log an error - // - -} - -HRESULT -IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( - VOID -) -{ - HRESULT hr = S_OK; - ENVIRONMENT_VAR_HASH* pHashTable = NULL; - if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( - m_pConfig->QueryEnvironmentVariables(), - m_pConfig->QueryWindowsAuthEnabled(), - m_pConfig->QueryBasicAuthEnabled(), - m_pConfig->QueryAnonymousAuthEnabled(), - &pHashTable))) - { - goto Finished; - } - - pHashTable->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr); - if (FAILED(hr)) - { - goto Finished; - } - pHashTable->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr); - if (FAILED(hr)) - { - goto Finished; - } -Finished: - return hr; -} - -HRESULT -IN_PROCESS_APPLICATION::ExecuteApplication( - VOID -) -{ - HRESULT hr = S_OK; - HMODULE hModule; - hostfxr_main_fn pProc; - - DBG_ASSERT(m_status == APPLICATION_STATUS::STARTING); - - hModule = LoadLibraryW(m_pConfig->QueryHostFxrFullPath()); - - if (hModule == NULL) - { - // .NET Core not installed (we can log a more detailed error message here) - hr = ERROR_BAD_ENVIRONMENT; - goto Finished; - } - - // Get the entry point for main - pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main"); - if (pProc == NULL) - { - hr = ERROR_BAD_ENVIRONMENT; - goto Finished; - } - - if (FAILED(hr = SetEnvironementVariablesOnWorkerProcess())) - { - goto Finished; - } - - // There can only ever be a single instance of .NET Core - // loaded in the process but we need to get config information to boot it up in the - // first place. This is happening in an execute request handler and everyone waits - // until this initialization is done. - // We set a static so that managed code can call back into this instance and - // set the callbacks - s_Application = this; - - hr = RunDotnetApplication(m_pConfig->QueryHostFxrArgCount(), m_pConfig->QueryHostFxrArguments(), pProc); - -Finished: - - // - // this method is called by the background thread and should never exit unless shutdown - // If main returned and shutdown was not called in managed, we want to block native from calling into - // managed. To do this, we can say that shutdown was called from managed. - // Don't bother locking here as there will always be a race between receiving a native shutdown - // notification and unexpected managed exit. - // - m_status = APPLICATION_STATUS::SHUTDOWN; - m_fShutdownCalledFromManaged = TRUE; - FreeLibrary(hModule); - - if (!m_fShutdownCalledFromNative) - { - LogErrorsOnMainExit(hr); - if (m_fInitialized) - { - // - // If the inprocess server was initialized, we need to cause recycle to be called on the worker process. - // - Recycle(); - } - } - - return hr; -} - -VOID -IN_PROCESS_APPLICATION::LogErrorsOnMainExit( - HRESULT hr -) -{ - // - // Ungraceful shutdown, try to log an error message. - // This will be a common place for errors as it means the hostfxr_main returned - // or there was an exception. - // - CHAR pzFileContents[4096] = { 0 }; - DWORD dwNumBytesRead; - STRU struStdErrLog; - LARGE_INTEGER li = { 0 }; - BOOL fLogged = FALSE; - DWORD dwFilePointer = 0; - - if (m_pConfig->QueryStdoutLogEnabled()) - { - // Put stdout/stderr logs into - if (m_hLogFileHandle != INVALID_HANDLE_VALUE) - { - if (GetFileSizeEx(m_hLogFileHandle, &li) && li.LowPart > 0 && li.HighPart == 0) - { - if (li.LowPart > 4096) - { - dwFilePointer = SetFilePointer(m_hLogFileHandle, -4096, NULL, FILE_END); - } - else - { - dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN); - } - if (dwFilePointer != INVALID_SET_FILE_POINTER) - { - if (ReadFile(m_hLogFileHandle, pzFileContents, 4096, &dwNumBytesRead, NULL)) - { - if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal))) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr, - struStdErrLog.QueryStr()); - fLogged = TRUE; - - } - } - } - } - } - } - else - { - if (m_dwStdErrReadTotal > 0) - { - if (SUCCEEDED(struStdErrLog.CopyA(m_pzFileContents, m_dwStdErrReadTotal))) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDERR_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr, - struStdErrLog.QueryStr()); - fLogged = TRUE; - } - } - } - - if (!fLogged) - { - // If we didn't log, log the generic message. - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, - ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - hr); - fLogged = TRUE; - } -} - -// -// Calls hostfxr_main with the hostfxr and application as arguments. -// Method should be called with only -// Need to have __try / __except in methods that require unwinding. -// Note, this will not -// -HRESULT -IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc) -{ - HRESULT hr = S_OK; - __try - { - m_ProcessExitCode = pProc(argc, argv); - } - __except (FilterException(GetExceptionCode(), GetExceptionInformation())) - { - // TODO Log error message here. - hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE; - } - - return hr; -} - -// static -INT -IN_PROCESS_APPLICATION::FilterException(unsigned int, struct _EXCEPTION_POINTERS*) -{ - // We assume that any exception is a failure as the applicaiton didn't start or there was a startup error. - // TODO, log error based on exception code. - return EXCEPTION_EXECUTE_HANDLER; -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.h deleted file mode 100644 index a97fa0a3a9..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocessapplication.h +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext); -typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); -typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_MANAGED_CONTEXT_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion); - -class IN_PROCESS_APPLICATION : public APPLICATION -{ -public: - IN_PROCESS_APPLICATION(IHttpServer* pHttpServer, ASPNETCORE_CONFIG* pConfig); - - ~IN_PROCESS_APPLICATION(); - - __override - VOID - ShutDown(); - - VOID - SetCallbackHandles( - _In_ PFN_REQUEST_HANDLER request_callback, - _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, - _In_ PFN_MANAGED_CONTEXT_HANDLER managed_context_callback, - _In_ VOID* pvRequstHandlerContext, - _In_ VOID* pvShutdownHandlerContext - ); - - __override - VOID - Recycle( - VOID - ); - - // Executes the .NET Core process - HRESULT - ExecuteApplication( - VOID - ); - - VOID - ReadStdErrHandleInternal( - VOID - ); - - VOID - CloseStdErrHandles( - VOID - ); - - HRESULT - LoadManagedApplication( - VOID - ); - - VOID - LogErrorsOnMainExit( - HRESULT hr - ); - - REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus, - IN_PROCESS_HANDLER* pInProcessHandler - ); - - REQUEST_NOTIFICATION_STATUS - OnExecuteRequest - ( - IHttpContext* pHttpContext, - IN_PROCESS_HANDLER* pInProcessHandler - ); - - VOID - StopCallsIntoManaged( - VOID - ) - { - m_fBlockCallbacksIntoManaged = TRUE; - } - - VOID - StopIncomingRequests( - VOID - ) - { - m_fShutdownCalledFromManaged = TRUE; - } - - static - IN_PROCESS_APPLICATION* - GetInstance( - VOID - ) - { - return s_Application; - } - -private: - static - VOID - DoShutDown( - LPVOID lpParam - ); - - VOID - ShutDownInternal( - VOID - ); - - IHttpServer* const m_pHttpServer; - - // Thread executing the .NET Core process - HANDLE m_hThread; - - // The request handler callback from managed code - PFN_REQUEST_HANDLER m_RequestHandler; - VOID* m_RequestHandlerContext; - - // The shutdown handler callback from managed code - PFN_SHUTDOWN_HANDLER m_ShutdownHandler; - VOID* m_ShutdownHandlerContext; - - PFN_MANAGED_CONTEXT_HANDLER m_AsyncCompletionHandler; - - // The event that gets triggered when managed initialization is complete - HANDLE m_pInitalizeEvent; - - // The std log file handle - HANDLE m_hLogFileHandle; - HANDLE m_hErrReadPipe; - HANDLE m_hErrWritePipe; - STRU m_struLogFilePath; - - // The exit code of the .NET Core process - INT m_ProcessExitCode; - - BOOL m_fIsWebSocketsConnection; - BOOL m_fDoneStdRedirect; - volatile BOOL m_fBlockCallbacksIntoManaged; - volatile BOOL m_fShutdownCalledFromNative; - volatile BOOL m_fShutdownCalledFromManaged; - BOOL m_fRecycleCalled; - BOOL m_fInitialized; - - FILE* m_pStdFile; - STTIMER m_Timer; - SRWLOCK m_srwLock; - - // Thread for capturing startup stderr logs when logging is disabled - HANDLE m_hErrThread; - CHAR m_pzFileContents[4096] = { 0 }; - DWORD m_dwStdErrReadTotal; - static IN_PROCESS_APPLICATION* s_Application; - - VOID - SetStdOut( - VOID - ); - - static - VOID - ExecuteAspNetCoreProcess( - _In_ LPVOID pContext - ); - - static - VOID - ReadStdErrHandle - ( - _In_ LPVOID pContext - ); - - HRESULT - SetEnvironementVariablesOnWorkerProcess( - VOID - ); - - static - INT - FilterException(unsigned int code, struct _EXCEPTION_POINTERS *ep); - - HRESULT - RunDotnetApplication( - DWORD argc, - CONST PCWSTR* argv, - hostfxr_main_fn pProc - ); -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.cpp deleted file mode 100644 index c98d84744c..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "..\precomp.hxx" - -IN_PROCESS_HANDLER::IN_PROCESS_HANDLER( - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication -): REQUEST_HANDLER(pW3Context, pModuleId, pApplication) -{ - m_fManagedRequestComplete = FALSE; -} - -IN_PROCESS_HANDLER::~IN_PROCESS_HANDLER() -{ - //todo -} - -__override -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_HANDLER::OnExecuteRequestHandler() -{ - // First get the in process Application - HRESULT hr; - hr = ((IN_PROCESS_APPLICATION*)m_pApplication)->LoadManagedApplication(); - if (FAILED(hr)) - { - // TODO remove com_error? - /*_com_error err(hr); - if (ANCMEvents::ANCM_START_APPLICATION_FAIL::IsEnabled(m_pW3Context->GetTraceContext())) - { - ANCMEvents::ANCM_START_APPLICATION_FAIL::RaiseEvent( - m_pW3Context->GetTraceContext(), - NULL, - err.ErrorMessage()); - } - */ - //fInternalError = TRUE; - m_pW3Context->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); - return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; - } - - // FREB log - - if (ANCMEvents::ANCM_START_APPLICATION_SUCCESS::IsEnabled(m_pW3Context->GetTraceContext())) - { - ANCMEvents::ANCM_START_APPLICATION_SUCCESS::RaiseEvent( - m_pW3Context->GetTraceContext(), - NULL, - L"InProcess Application"); - } - - //SetHttpSysDisconnectCallback(); - return ((IN_PROCESS_APPLICATION*)m_pApplication)->OnExecuteRequest(m_pW3Context, this); -} - -__override -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_HANDLER::OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus -) -{ - IN_PROCESS_APPLICATION* application = (IN_PROCESS_APPLICATION*)m_pApplication; - if (application == NULL) - { - return RQ_NOTIFICATION_FINISH_REQUEST; - } - - // OnAsyncCompletion must call into the application if there was a error. We will redo calls - // to Read/Write if we called cancelIo on the IHttpContext. - return application->OnAsyncCompletion(cbCompletion, hrCompletionStatus, this); -} - -VOID -IN_PROCESS_HANDLER::TerminateRequest( - bool fClientInitiated -) -{ - UNREFERENCED_PARAMETER(fClientInitiated); - //todo -} - -PVOID -IN_PROCESS_HANDLER::QueryManagedHttpContext( - VOID -) -{ - return m_pManagedHttpContext; -} - -BOOL -IN_PROCESS_HANDLER::QueryIsManagedRequestComplete( - VOID -) -{ - return m_fManagedRequestComplete; -} - -IHttpContext* -IN_PROCESS_HANDLER::QueryHttpContext( - VOID -) -{ - return m_pW3Context; -} - -VOID -IN_PROCESS_HANDLER::IndicateManagedRequestComplete( - VOID -) -{ - m_fManagedRequestComplete = TRUE; -} - -REQUEST_NOTIFICATION_STATUS -IN_PROCESS_HANDLER::QueryAsyncCompletionStatus( - VOID -) -{ - return m_requestNotificationStatus; -} - -VOID -IN_PROCESS_HANDLER::SetAsyncCompletionStatus( - REQUEST_NOTIFICATION_STATUS requestNotificationStatus -) -{ - m_requestNotificationStatus = requestNotificationStatus; -} - -VOID -IN_PROCESS_HANDLER::SetManagedHttpContext( - PVOID pManagedHttpContext -) -{ - m_pManagedHttpContext = pManagedHttpContext; -} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.h deleted file mode 100644 index 1ea1a8dbc5..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/inprocess/inprocesshandler.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -class IN_PROCESS_HANDLER : public REQUEST_HANDLER -{ -public: - IN_PROCESS_HANDLER( - - _In_ IHttpContext *pW3Context, - _In_ HTTP_MODULE_ID *pModuleId, - _In_ APPLICATION *pApplication); - - ~IN_PROCESS_HANDLER(); - - __override - REQUEST_NOTIFICATION_STATUS - OnExecuteRequestHandler(); - - __override - REQUEST_NOTIFICATION_STATUS - OnAsyncCompletion( - DWORD cbCompletion, - HRESULT hrCompletionStatus - ); - - __override - VOID - TerminateRequest( - bool fClientInitiated - - ); - - PVOID - QueryManagedHttpContext( - VOID - ); - - VOID - SetManagedHttpContext( - PVOID pManagedHttpContext - ); - - IHttpContext* - QueryHttpContext( - VOID - ); - - BOOL - QueryIsManagedRequestComplete( - VOID - ); - - VOID - IndicateManagedRequestComplete( - VOID - ); - - REQUEST_NOTIFICATION_STATUS - QueryAsyncCompletionStatus( - VOID - ); - - VOID - SetAsyncCompletionStatus( - REQUEST_NOTIFICATION_STATUS requestNotificationStatus - ); - -private: - PVOID m_pManagedHttpContext; - IHttpContext* m_pHttpContext; - BOOL m_fManagedRequestComplete; - REQUEST_NOTIFICATION_STATUS m_requestNotificationStatus; -}; \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.cpp deleted file mode 100644 index e9222ba95e..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "..\precomp.hxx" - -OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION( - IHttpServer* pHttpServer, - ASPNETCORE_CONFIG* pConfig) : - APPLICATION(pHttpServer, pConfig) -{ - m_status = APPLICATION_STATUS::RUNNING; - m_pProcessManager = NULL; - InitializeSRWLock(&rwlock); -} - -OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION() -{ - if (m_pProcessManager != NULL) - { - m_pProcessManager->ShutdownAllProcesses(); - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } -} - -HRESULT -OUT_OF_PROCESS_APPLICATION::Initialize( -) -{ - HRESULT hr = S_OK; - if (m_pProcessManager == NULL) - { - m_pProcessManager = new PROCESS_MANAGER; - if (m_pProcessManager == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = m_pProcessManager->Initialize(); - if (FAILED(hr)) - { - goto Finished; - } - } - -Finished: - return hr; -} - -HRESULT -OUT_OF_PROCESS_APPLICATION::GetProcess( - _Out_ SERVER_PROCESS **ppServerProcess -) -{ - return m_pProcessManager->GetProcess(m_pConfig, ppServerProcess); -} - -__override -VOID -OUT_OF_PROCESS_APPLICATION::ShutDown() -{ - AcquireSRWLockExclusive(&rwlock); - { - if (m_pProcessManager != NULL) - { - m_pProcessManager->ShutdownAllProcesses(); - m_pProcessManager->DereferenceProcessManager(); - m_pProcessManager = NULL; - } - } - ReleaseSRWLockExclusive(&rwlock); -} - -__override -VOID -OUT_OF_PROCESS_APPLICATION::Recycle() -{ - ShutDown(); -} - diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.h deleted file mode 100644 index f8c30a69cc..0000000000 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/outofprocess/outprocessapplication.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -class OUT_OF_PROCESS_APPLICATION : public APPLICATION -{ - -public: - OUT_OF_PROCESS_APPLICATION(IHttpServer* pHttpServer, ASPNETCORE_CONFIG *pConfig); - - ~OUT_OF_PROCESS_APPLICATION(); - - HRESULT - Initialize(); - - HRESULT - GetProcess( - _Out_ SERVER_PROCESS **ppServerProcess - ); - - __override - VOID - ShutDown(); - - __override - VOID - Recycle(); - -private: - PROCESS_MANAGER * m_pProcessManager; - SRWLOCK rwlock; -}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp new file mode 100644 index 0000000000..a946e7c2dc --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -0,0 +1,66 @@ +// 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. + +#include "stdafx.h" +#include "AppOfflineTrackingApplication.h" +#include "EventLog.h" +#include "exceptions.h" + +HRESULT AppOfflineTrackingApplication::StartMonitoringAppOffline() +{ + LOG_INFOF(L"Starting app_offline monitoring in application '%ls'", m_applicationPath.c_str()); + HRESULT hr = StartMonitoringAppOflineImpl(); + + if (FAILED_LOG(hr)) + { + EventLog::Warn( + ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR, + ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR_MSG, + m_applicationPath.c_str(), + hr); + } + + return hr; +} + +void AppOfflineTrackingApplication::StopInternal(bool fServerInitiated) +{ + APPLICATION::StopInternal(fServerInitiated); + + if (m_fileWatcher) + { + m_fileWatcher->StopMonitor(); + m_fileWatcher = nullptr; + } +} + +HRESULT AppOfflineTrackingApplication::StartMonitoringAppOflineImpl() +{ + if (m_fileWatcher) + { + RETURN_HR(E_UNEXPECTED); + } + + m_fileWatcher = std::make_unique(); + RETURN_IF_FAILED(m_fileWatcher->Create(m_applicationPath.c_str(), + L"app_offline.htm", + this)); + + return S_OK; +} + +void AppOfflineTrackingApplication::OnAppOffline() +{ + if (m_fAppOfflineProcessed.exchange(true)) + { + return; + } + + LOG_INFOF(L"Received app_offline notification in application '%ls'", m_applicationPath.c_str()); + EventLog::Info( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, + m_applicationPath.c_str()); + + Stop(/*fServerInitiated*/ false); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h new file mode 100644 index 0000000000..b504a730fd --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include +#include "application.h" +#include "filewatcher.h" +#include + +class AppOfflineTrackingApplication: public APPLICATION +{ +public: + AppOfflineTrackingApplication(const IHttpApplication& application) + : APPLICATION(application), + m_applicationPath(application.GetApplicationPhysicalPath()), + m_fileWatcher(nullptr), + m_fAppOfflineProcessed(false) + { + } + + ~AppOfflineTrackingApplication() override + { + if (m_fileWatcher) + { + m_fileWatcher->StopMonitor(); + } + }; + + HRESULT + StartMonitoringAppOffline(); + + VOID + StopInternal(bool fServerInitiated) override; + + virtual + VOID + OnAppOffline(); + +private: + HRESULT + StartMonitoringAppOflineImpl(); + + std::wstring m_applicationPath; + std::unique_ptr m_fileWatcher; + std::atomic_bool m_fAppOfflineProcessed; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj new file mode 100644 index 0000000000..2519c101b1 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/RequestHandlerLib.vcxproj @@ -0,0 +1,220 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {1533E271-F61B-441B-8B74-59FB61DF0552} + Win32Proj + NewCommon + 10.0.15063.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + false + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + true + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + false + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + false + C:\AspNetCoreModule\src\IISLib;$(IncludePath) + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + + NotUsing + Level4 + true + Disabled + false + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + MultiThreadedDebug + false + ProgramDatabase + ..\iislib;..\CommonLib; + true + stdcpp17 + + + Windows + true + + + + + NotUsing + Level4 + true + Disabled + false + _DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + ProgramDatabase + false + MultiThreadedDebug + false + ..\iislib;..\CommonLib; + true + stdcpp17 + + + Windows + true + + + + + NotUsing + Level4 + true + MaxSpeed + true + true + false + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + MultiThreaded + false + ..\iislib;..\CommonLib; + true + stdcpp17 + + + Windows + true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + true + + + + + NotUsing + Level4 + true + MaxSpeed + true + true + false + NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + ..\iislib;..\CommonLib; + true + + + MultiThreaded + false + stdcpp17 + + + Windows + true + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + true + + + ..\iislib + + + + + + + + + + + + + + + + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} + + + + + + \ No newline at end of file diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/environmentvariablehash.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h similarity index 87% rename from src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/environmentvariablehash.h rename to src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h index 8fa054f2a9..c5f63f6fde 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/environmentvariablehash.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h @@ -33,13 +33,13 @@ public: { HRESULT hr = S_OK; if (FAILED(hr = _strName.Copy(pszName)) || - FAILED(hr = _strValue.Copy(pszValue))) + FAILED(hr = _strValue.Copy(pszValue))) { } - return hr; + return hr; } - VOID + VOID Reference() const { InterlockedIncrement(&_cRefs); @@ -76,6 +76,7 @@ private: mutable LONG _cRefs; }; + class ENVIRONMENT_VAR_HASH : public HASH_TABLE { public: @@ -129,3 +130,20 @@ private: ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); void operator=(const ENVIRONMENT_VAR_HASH &); }; + +struct ENVIRONMENT_VAR_HASH_DELETER +{ + void operator ()(ENVIRONMENT_VAR_HASH* hashTable) const + { + hashTable->Clear(); + delete hashTable; + } +}; + +struct ENVIRONMENT_VAR_ENTRY_DELETER +{ + void operator ()(ENVIRONMENT_VAR_ENTRY* entry) const + { + entry->Dereference(); + } +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/environmentvariablehelpers.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h similarity index 95% rename from src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/environmentvariablehelpers.h rename to src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h index 268011d30a..b65c32ee29 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandler/environmentvariablehelpers.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h @@ -3,8 +3,6 @@ #pragma once -#include "precomp.hxx" - class ENVIRONMENT_VAR_HELPERS { @@ -98,9 +96,10 @@ public: dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), struValueBuffer.QueryStr(), struValueBuffer.QuerySizeCCH()); - if (struValueBuffer.IsEmpty()) + + if (dwResult <= 0) { - hr = E_UNEXPECTED; + hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } fFound = TRUE; @@ -205,11 +204,6 @@ public: ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); - if (pEnvironmentVarTable == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } // // few environment variables expected, small bucket size for hash table @@ -257,11 +251,7 @@ public: } pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pIISAuthEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } + if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) { @@ -304,7 +294,7 @@ public: dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, strStartupAssemblyEnv.QueryStr(), strStartupAssemblyEnv.QuerySizeCCH()); - if (strStartupAssemblyEnv.IsEmpty()) + if (dwResult <= 0) { hr = E_UNEXPECTED; goto Finished; @@ -317,19 +307,18 @@ public: } strStartupAssemblyEnv.SyncWithBuffer(); + if (strStartupAssemblyEnv.IndexOf(HOSTING_STARTUP_ASSEMBLIES_VALUE) == -1) + { if (fFound) { strStartupAssemblyEnv.Append(L";"); } strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); + } // the environment variable was not defined, create it and add to hashtable pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pHostingEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } + if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) { @@ -362,7 +351,6 @@ public: return hr; } - static HRESULT AddWebsocketEnabledToEnvironmentVariables @@ -400,11 +388,7 @@ public: } pIISWebsocketEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pIISWebsocketEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } + if (FAILED(hr = pIISWebsocketEntry->Initialize(ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, strIISWebsocketEnvValue.QueryStr())) || FAILED(hr = pInEnvironmentVarTable->InsertRecord(pIISWebsocketEntry))) { diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp new file mode 100644 index 0000000000..9d17f95b37 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -0,0 +1,327 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "filewatcher.h" +#include "debugutil.h" +#include "AppOfflineTrackingApplication.h" +#include "exceptions.h" + +FILE_WATCHER::FILE_WATCHER() : + m_hCompletionPort(NULL), + m_hChangeNotificationThread(NULL), + m_fThreadExit(FALSE) +{ +} + +FILE_WATCHER::~FILE_WATCHER() +{ + StopMonitor(); + + if (m_hChangeNotificationThread != NULL) + { + DWORD dwRetryCounter = 20; // totally wait for 1s + DWORD dwExitCode = STILL_ACTIVE; + + while (!m_fThreadExit && dwRetryCounter > 0) + { + if (GetExitCodeThread(m_hChangeNotificationThread, &dwExitCode)) + { + if (dwExitCode == STILL_ACTIVE) + { + // the file watcher thread will set m_fThreadExit before exit + WaitForSingleObject(m_hChangeNotificationThread, 50); + } + } + else + { + // fail to get thread status + // call terminitethread + TerminateThread(m_hChangeNotificationThread, 1); + m_fThreadExit = TRUE; + } + dwRetryCounter--; + } + + if (!m_fThreadExit) + { + TerminateThread(m_hChangeNotificationThread, 1); + } + } +} + +HRESULT +FILE_WATCHER::Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ AppOfflineTrackingApplication *pApplication +) +{ + + RETURN_LAST_ERROR_IF_NULL(m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); + + RETURN_LAST_ERROR_IF_NULL(m_hChangeNotificationThread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE)ChangeNotificationThread, + this, + 0, + NULL)); + + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplication == NULL) + { + DBG_ASSERT(FALSE); + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + _pApplication = ReferenceApplication(pApplication); + + RETURN_IF_FAILED(_strFileName.Copy(pszFileNameToMonitor)); + RETURN_IF_FAILED(_strDirectoryName.Copy(pszDirectoryToMonitor)); + RETURN_IF_FAILED(_strFullName.Append(_strDirectoryName)); + RETURN_IF_FAILED(_strFullName.Append(_strFileName)); + + // + // Resize change buffer to something "reasonable" + // + RETURN_LAST_ERROR_IF(!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)); + + _hDirectory = CreateFileW( + _strDirectoryName.QueryStr(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + + RETURN_LAST_ERROR_IF_NULL(_hDirectory); + + RETURN_LAST_ERROR_IF_NULL(CreateIoCompletionPort( + _hDirectory, + m_hCompletionPort, + NULL, + 0)); + + RETURN_IF_FAILED(Monitor()); + + return S_OK; +} + +DWORD +WINAPI +FILE_WATCHER::ChangeNotificationThread( + LPVOID pvArg +) +/*++ + +Routine Description: + +IO completion thread + +Arguments: + +None + +Return Value: + +Win32 error + +--*/ +{ + FILE_WATCHER * pFileMonitor; + BOOL fSuccess = FALSE; + DWORD cbCompletion = 0; + OVERLAPPED * pOverlapped = NULL; + DWORD dwErrorStatus; + ULONG_PTR completionKey; + + LOG_INFO(L"Starting file watcher thread"); + pFileMonitor = (FILE_WATCHER*)pvArg; + DBG_ASSERT(pFileMonitor != NULL); + + while (TRUE) + { + fSuccess = GetQueuedCompletionStatus( + pFileMonitor->m_hCompletionPort, + &cbCompletion, + &completionKey, + &pOverlapped, + INFINITE); + + DBG_ASSERT(fSuccess); + dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); + + if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) + { + break; + } + + DBG_ASSERT(pOverlapped != NULL); + if (pOverlapped != NULL) + { + pFileMonitor->HandleChangeCompletion(cbCompletion); + + if (!pFileMonitor->_lStopMonitorCalled) + { + // + // Continue monitoring + // + pFileMonitor->Monitor(); + } + } + pOverlapped = NULL; + cbCompletion = 0; + } + + pFileMonitor->m_fThreadExit = TRUE; + + LOG_INFO(L"Stopping file watcher thread"); + ExitThread(0); +} + + +HRESULT +FILE_WATCHER::HandleChangeCompletion( + _In_ DWORD cbCompletion +) +/*++ + +Routine Description: + +Handle change notification (see if any of associated config files +need to be flushed) + +Arguments: + +dwCompletionStatus - Completion status +cbCompletion - Bytes of completion + +Return Value: + +HRESULT + +--*/ +{ + BOOL fFileChanged = FALSE; + + // When directory handle is closed then HandleChangeCompletion + // happens with cbCompletion = 0 and dwCompletionStatus = 0 + // From documentation it is not clear if that combination + // of return values is specific to closing handles or whether + // it could also mean an error condition. Hence we will maintain + // explicit flag that will help us determine if entry + // is being shutdown (StopMonitor() called) + // + if (_lStopMonitorCalled) + { + return S_OK; + } + + // + // There could be a FCN overflow + // Let assume the file got changed instead of checking files + // Othersie we have to cache the file info + // + if (cbCompletion == 0) + { + fFileChanged = TRUE; + } + else + { + auto pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + DBG_ASSERT(pNotificationInfo != NULL); + + while (pNotificationInfo != NULL) + { + // + // check whether the monitored file got changed + // + if (_wcsnicmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength / sizeof(WCHAR)) == 0) + { + fFileChanged = TRUE; + break; + } + // + // Advance to next notification + // + if (pNotificationInfo->NextEntryOffset == 0) + { + pNotificationInfo = NULL; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*) + ((PBYTE)pNotificationInfo + + pNotificationInfo->NextEntryOffset); + } + } + } + + if (fFileChanged && !_lStopMonitorCalled) + { + // Reference application before + _pApplication->ReferenceApplication(); + RETURN_LAST_ERROR_IF(!QueueUserWorkItem(RunNotificationCallback, _pApplication.get(), WT_EXECUTEDEFAULT)); + } + + return S_OK; +} + +DWORD +WINAPI +FILE_WATCHER::RunNotificationCallback( + LPVOID pvArg +) +{ + // Recapture application instance into unique_ptr + auto pApplication = std::unique_ptr(static_cast(pvArg)); + DBG_ASSERT(pFileMonitor != NULL); + pApplication->OnAppOffline(); + + return 0; +} + +HRESULT +FILE_WATCHER::Monitor(VOID) +{ + HRESULT hr = S_OK; + DWORD cbRead; + + ZeroMemory(&_overlapped, sizeof(_overlapped)); + + RETURN_LAST_ERROR_IF(!ReadDirectoryChangesW(_hDirectory, + _buffDirectoryChanges.QueryPtr(), + _buffDirectoryChanges.QuerySize(), + FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, + &cbRead, + &_overlapped, + NULL)); + + // Check if file exist because ReadDirectoryChangesW would not fire events for existing files + if (GetFileAttributes(_strFullName.QueryStr()) != INVALID_FILE_ATTRIBUTES) + { + PostQueuedCompletionStatus(m_hCompletionPort, 0, 0, &_overlapped); + } + + return hr; +} + +VOID +FILE_WATCHER::StopMonitor() +{ + // + // Flag that monitoring is being stopped so that + // we know that HandleChangeCompletion() call + // can be ignored + // + InterlockedExchange(&_lStopMonitorCalled, 1); + // signal the file watch thread to exit + PostQueuedCompletionStatus(m_hCompletionPort, 0, FILE_WATCHER_SHUTDOWN_KEY, NULL); + // Release application reference + _pApplication.reset(nullptr); +} diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h new file mode 100644 index 0000000000..46ea744533 --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.h @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + + +#pragma once + +#include +#include +#include "iapplication.h" +#include "HandleWrapper.h" + +#define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) +#define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 +#define FILE_NOTIFY_VALID_MASK 0x00000fff + +class AppOfflineTrackingApplication; + +class FILE_WATCHER{ +public: + + FILE_WATCHER(); + + ~FILE_WATCHER(); + + HRESULT Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ AppOfflineTrackingApplication *pApplication + ); + + static + DWORD + WINAPI ChangeNotificationThread(LPVOID); + + static + DWORD + WINAPI RunNotificationCallback(LPVOID); + + HRESULT HandleChangeCompletion(DWORD cbCompletion); + + HRESULT Monitor(); + void StopMonitor(); + +private: + HandleWrapper m_hCompletionPort; + HandleWrapper m_hChangeNotificationThread; + HandleWrapper _hDirectory; + volatile BOOL m_fThreadExit; + + BUFFER _buffDirectoryChanges; + STRU _strFileName; + STRU _strDirectoryName; + STRU _strFullName; + LONG _lStopMonitorCalled {}; + OVERLAPPED _overlapped; + std::unique_ptr _pApplication; +}; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.cxx b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp similarity index 72% rename from src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.cxx rename to src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp index 97a7d0c63e..1a087d5c95 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.cxx +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp @@ -2,10 +2,14 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #include "stdafx.h" -#include "aspnetcoreconfig.h" +#include "requesthandler_config.h" #include "debugutil.h" +#include "environmentvariablehash.h" +#include "exceptions.h" +#include "config_utility.h" -ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() + +REQUESTHANDLER_CONFIG::~REQUESTHANDLER_CONFIG() { if (m_ppStrArguments != NULL) { @@ -21,150 +25,67 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() } } -VOID -ASPNETCORE_CONFIG::ReferenceConfiguration( - VOID -) const -{ - InterlockedIncrement(&m_cRefs); -} - -VOID -ASPNETCORE_CONFIG::DereferenceConfiguration( - VOID -) const -{ - DBG_ASSERT(m_cRefs != 0); - LONG cRefs = 0; - if ((cRefs = InterlockedDecrement(&m_cRefs)) == 0) - { - delete this; - } -} - HRESULT -ASPNETCORE_CONFIG::GetConfig( +REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig( _In_ IHttpServer *pHttpServer, - _In_ HTTP_MODULE_ID pModuleId, - _In_ IHttpContext *pHttpContext, - _In_ HANDLE hEventLog, - _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + _In_ IHttpApplication *pHttpApplication, + _Out_ REQUESTHANDLER_CONFIG **ppAspNetCoreConfig ) { HRESULT hr = S_OK; - IHttpApplication *pHttpApplication = pHttpContext->GetApplication(); - ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; + REQUESTHANDLER_CONFIG *pRequestHandlerConfig = NULL; STRU struHostFxrDllLocation; - PWSTR* pwzArgv; - DWORD dwArgCount; + STRU struExeLocation; - if (ppAspNetCoreConfig == NULL) + try { - hr = E_INVALIDARG; - goto Finished; - } - - *ppAspNetCoreConfig = NULL; - - // potential bug if user sepcific config at virtual dir level - pAspNetCoreConfig = (ASPNETCORE_CONFIG*) - pHttpApplication->GetModuleContextContainer()->GetModuleContext(pModuleId); - - if (pAspNetCoreConfig != NULL) - { - *ppAspNetCoreConfig = pAspNetCoreConfig; - pAspNetCoreConfig = NULL; - goto Finished; - } - - pAspNetCoreConfig = new ASPNETCORE_CONFIG; - if (pAspNetCoreConfig == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pAspNetCoreConfig->Populate(pHttpServer, pHttpContext); - if (FAILED(hr)) - { - goto Finished; - } - - // Modify config for inprocess. - if (pAspNetCoreConfig->QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) - { - if (FAILED(hr = HOSTFXR_UTILITY::GetHostFxrParameters( - hEventLog, - pAspNetCoreConfig->QueryProcessPath()->QueryStr(), - pAspNetCoreConfig->QueryApplicationPhysicalPath()->QueryStr(), - pAspNetCoreConfig->QueryArguments()->QueryStr(), - &struHostFxrDllLocation, - &dwArgCount, - &pwzArgv))) + if (ppAspNetCoreConfig == NULL) { + hr = E_INVALIDARG; goto Finished; } - if (FAILED(hr = pAspNetCoreConfig->SetHostFxrFullPath(struHostFxrDllLocation.QueryStr()))) - { - goto Finished; - } + *ppAspNetCoreConfig = NULL; - pAspNetCoreConfig->SetHostFxrArguments(dwArgCount, pwzArgv); - } + pRequestHandlerConfig = new REQUESTHANDLER_CONFIG; - hr = pHttpApplication->GetModuleContextContainer()-> - SetModuleContext(pAspNetCoreConfig, pModuleId); - if (FAILED(hr)) - { - if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) - { - delete pAspNetCoreConfig; - - pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication-> - GetModuleContextContainer()-> - GetModuleContext(pModuleId); - - _ASSERT(pAspNetCoreConfig != NULL); - - hr = S_OK; - } - else - { - goto Finished; - } - } - else - { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, - "ASPNETCORE_CONFIG::GetConfig, set config to ModuleContext"); - // set appliction info here instead of inside Populate() - // as the destructor will delete the backend process - hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); + hr = pRequestHandlerConfig->Populate(pHttpServer, pHttpApplication); if (FAILED(hr)) { goto Finished; } - } - *ppAspNetCoreConfig = pAspNetCoreConfig; - pAspNetCoreConfig = NULL; + // set appliction info here instead of inside Populate() + // as the destructor will delete the backend process + hr = pRequestHandlerConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); + if (FAILED(hr)) + { + goto Finished; + } + + *ppAspNetCoreConfig = pRequestHandlerConfig; + pRequestHandlerConfig = NULL; + } + catch (std::bad_alloc&) + { + hr = E_OUTOFMEMORY; + } Finished: - if (pAspNetCoreConfig != NULL) + if (pRequestHandlerConfig != NULL) { - delete pAspNetCoreConfig; - pAspNetCoreConfig = NULL; + delete pRequestHandlerConfig; + pRequestHandlerConfig = NULL; } return hr; } HRESULT -ASPNETCORE_CONFIG::Populate( +REQUESTHANDLER_CONFIG::Populate( IHttpServer *pHttpServer, - IHttpContext *pHttpContext + IHttpApplication *pHttpApplication ) { STACK_STRU(strHostingModel, 300); @@ -178,7 +99,6 @@ ASPNETCORE_CONFIG::Populate( IAppHostElement *pWindowsAuthenticationElement = NULL; IAppHostElement *pBasicAuthenticationElement = NULL; IAppHostElement *pAnonymousAuthenticationElement = NULL; - IAppHostElement *pWebSocketElement = NULL; IAppHostElement *pEnvVarList = NULL; IAppHostElement *pEnvVar = NULL; IAppHostElementCollection *pEnvVarCollection = NULL; @@ -192,14 +112,8 @@ ASPNETCORE_CONFIG::Populate( BSTR bstrBasicAuthSection = NULL; BSTR bstrAnonymousAuthSection = NULL; BSTR bstrAspNetCoreSection = NULL; - BSTR bstrWebsocketSection = NULL; m_pEnvironmentVariables = new ENVIRONMENT_VAR_HASH(); - if (m_pEnvironmentVariables == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } if (FAILED(hr = m_pEnvironmentVariables->Initialize(37 /*prime*/))) { delete m_pEnvironmentVariables; @@ -208,13 +122,13 @@ ASPNETCORE_CONFIG::Populate( } pAdminManager = pHttpServer->GetAdminManager(); - hr = m_struConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); + hr = m_struConfigPath.Copy(pHttpApplication->GetAppConfigPath()); if (FAILED(hr)) { goto Finished; } - hr = m_struApplicationPhysicalPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); + hr = m_struApplicationPhysicalPath.Copy(pHttpApplication->GetApplicationPhysicalPath()); if (FAILED(hr)) { goto Finished; @@ -253,7 +167,6 @@ ASPNETCORE_CONFIG::Populate( hr = E_OUTOFMEMORY; goto Finished; } - hr = pAdminManager->GetAdminSection(bstrWindowAuthSection, m_struConfigPath.QueryStr(), &pWindowsAuthenticationElement); @@ -281,6 +194,7 @@ ASPNETCORE_CONFIG::Populate( hr = E_OUTOFMEMORY; goto Finished; } + hr = pAdminManager->GetAdminSection(bstrBasicAuthSection, m_struConfigPath.QueryStr(), &pBasicAuthenticationElement); @@ -298,12 +212,14 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } } + bstrAnonymousAuthSection = SysAllocString(CS_ANONYMOUS_AUTHENTICATION_SECTION); if (bstrAnonymousAuthSection == NULL) { hr = E_OUTOFMEMORY; goto Finished; } + hr = pAdminManager->GetAdminSection(bstrAnonymousAuthSection, m_struConfigPath.QueryStr(), &pAnonymousAuthenticationElement); @@ -322,31 +238,6 @@ ASPNETCORE_CONFIG::Populate( } } - bstrWebsocketSection = SysAllocString(CS_WEBSOCKET_SECTION); - if (bstrWebsocketSection == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - hr = pAdminManager->GetAdminSection(bstrWebsocketSection, - m_struConfigPath.QueryStr(), - &pWebSocketElement); - if (FAILED(hr)) - { - m_fWebSocketEnabled = FALSE; - } - else - { - hr = GetElementBoolProperty(pWebSocketElement, - CS_ENABLED, - &m_fWebSocketEnabled); - if (FAILED(hr)) - { - goto Finished; - } - } - bstrAspNetCoreSection = SysAllocString(CS_ASPNETCORE_SECTION); if (bstrAspNetCoreSection == NULL) { @@ -529,11 +420,6 @@ ASPNETCORE_CONFIG::Populate( } pEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } if (FAILED(hr = pEntry->Initialize(strEnvName.QueryStr(), strExpandedEnvValue.QueryStr())) || FAILED(hr = m_pEnvironmentVariables->InsertRecord(pEntry))) @@ -557,12 +443,6 @@ Finished: pAspNetCoreElement = NULL; } - if (pWebSocketElement != NULL) - { - pWebSocketElement->Release(); - pWebSocketElement = NULL; - } - if (pWindowsAuthenticationElement != NULL) { pWindowsAuthenticationElement->Release(); diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h similarity index 77% rename from src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.h rename to src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h index 655dcdc385..5ba8959317 100644 --- a/src/IISIntegration/src/AspNetCoreModuleV2/CommonLib/aspnetcoreconfig.h +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h @@ -42,6 +42,7 @@ //#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) #include "stdafx.h" +#include "environmentvariablehash.h" enum APP_HOSTING_MODEL { @@ -50,27 +51,19 @@ enum APP_HOSTING_MODEL HOSTING_OUT_PROCESS }; -class ASPNETCORE_CONFIG : IHttpStoredContext +class REQUESTHANDLER_CONFIG { public: - virtual - ~ASPNETCORE_CONFIG(); - VOID - CleanupStoredContext() - { - DereferenceConfiguration(); - } + ~REQUESTHANDLER_CONFIG(); static HRESULT - GetConfig( + CreateRequestHandlerConfig( _In_ IHttpServer *pHttpServer, - _In_ HTTP_MODULE_ID pModuleId, - _In_ IHttpContext *pHttpContext, - _In_ HANDLE hEventLog, - _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + _In_ IHttpApplication *pHttpApplication, + _Out_ REQUESTHANDLER_CONFIG **ppAspNetCoreConfig ); ENVIRONMENT_VAR_HASH* @@ -155,7 +148,7 @@ public: STRU* QueryProcessPath( - VOID + VOID ) { return &m_struProcessPath; @@ -175,12 +168,6 @@ public: return m_fStdoutLogEnabled; } - BOOL - QueryWebSocketEnabled() - { - return m_fWebSocketEnabled; - } - BOOL QueryForwardWindowsAuthToken() { @@ -223,88 +210,25 @@ public: return &m_struConfigPath; } - CONST - PCWSTR* - QueryHostFxrArguments( - VOID - ) - { - return m_ppStrArguments; - } - - CONST - DWORD - QueryHostFxrArgCount( - VOID - ) - { - return m_dwArgc; - } - - CONST - PCWSTR - QueryHostFxrFullPath( - VOID - ) - { - return m_struHostFxrLocation.QueryStr(); - } - - HRESULT - SetHostFxrFullPath( - PCWSTR pStrHostFxrFullPath - ) - { - return m_struHostFxrLocation.Copy(pStrHostFxrFullPath); - } - - VOID - SetHostFxrArguments( - DWORD dwArgc, - PWSTR* ppStrArguments - ) - { - if (m_ppStrArguments != NULL) - { - delete[] m_ppStrArguments; - } - - m_dwArgc = dwArgc; - m_ppStrArguments = ppStrArguments; - } - - VOID - ReferenceConfiguration( - VOID - ) const; - - VOID - DereferenceConfiguration( - VOID - ) const; - -private: +protected: // - // private constructor - // - ASPNETCORE_CONFIG(): - m_fStdoutLogEnabled( FALSE ), - m_pEnvironmentVariables( NULL ), - m_cRefs( 1 ), - m_hostingModel( HOSTING_UNKNOWN ), + // protected constructor + // + REQUESTHANDLER_CONFIG() : + m_fStdoutLogEnabled(FALSE), + m_pEnvironmentVariables(NULL), + m_hostingModel(HOSTING_UNKNOWN), m_ppStrArguments(NULL) { } HRESULT Populate( - IHttpServer *pHttpServer, - IHttpContext *pHttpContext + IHttpServer *pHttpServer, + IHttpApplication *pHttpApplication ); - mutable LONG m_cRefs; - DWORD m_dwRequestTimeoutInMS; DWORD m_dwStartupTimeLimitInMS; DWORD m_dwShutdownTimeLimitInMS; @@ -323,10 +247,10 @@ private: BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; - BOOL m_fWebSocketEnabled; APP_HOSTING_MODEL m_hostingModel; ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; STRU m_struHostFxrLocation; - PWSTR* m_ppStrArguments; + PWSTR* m_ppStrArguments; DWORD m_dwArgc; + }; diff --git a/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h new file mode 100644 index 0000000000..78a02ce72b --- /dev/null +++ b/src/IISIntegration/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Shlwapi.h" +#include + +#include "hashtable.h" +#include "stringu.h" +#include "stringa.h" +#include "multisz.h" +#include "dbgutil.h" +#include "ahutil.h" +#include "hashfn.h" diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/AssemblyInfo.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/AssemblyInfo.cs new file mode 100644 index 0000000000..f08839f842 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.IISIntegration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs new file mode 100644 index 0000000000..8ff01c778f --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.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 System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + // See https://github.com/aspnet/IISIntegration/issues/426 + internal class DuplexStream : Stream + { + private Stream _requestBody; + private Stream _responseBody; + + public DuplexStream(Stream requestBody, Stream responseBody) + { + _requestBody = requestBody; + _responseBody = responseBody; + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override void Flush() + { + _responseBody.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _requestBody.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _responseBody.Write(buffer, offset, count); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _requestBody.ReadAsync(buffer, offset, count, cancellationToken); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _responseBody.WriteAsync(buffer, offset, count, cancellationToken); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs new file mode 100644 index 0000000000..dfc804df42 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class EmptyStream : ReadOnlyStream + { + private readonly IHttpBodyControlFeature _bodyControl; + private HttpStreamState _state; + private Exception _error; + + public EmptyStream(IHttpBodyControlFeature bodyControl) + { + _bodyControl = bodyControl; + _state = HttpStreamState.Open; + } + + public override void Flush() + { + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (!_bodyControl.AllowSynchronousIO) + { + throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed); + } + + return 0; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateState(cancellationToken); + + return Task.FromResult(0); + } + + public void StopAcceptingReads() + { + // Can't use dispose (or close) as can be disposed too early by user code + // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes + _state = HttpStreamState.Closed; + } + + public void Abort(Exception error = null) + { + // We don't want to throw an ODE until the app func actually completes. + // If the request is aborted, we throw a TaskCanceledException instead, + // unless error is not null, in which case we throw it. + if (_state != HttpStreamState.Closed) + { + _state = HttpStreamState.Aborted; + _error = error; + } + } + + private void ValidateState(CancellationToken cancellationToken) + { + switch (_state) + { + case HttpStreamState.Open: + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + break; + case HttpStreamState.Closed: + throw new ObjectDisposedException(nameof(HttpRequestStream)); + case HttpStreamState.Aborted: + if (_error != null) + { + ExceptionDispatchInfo.Capture(_error).Throw(); + } + else + { + throw new TaskCanceledException(); + } + break; + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs new file mode 100644 index 0000000000..8a9d37b146 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs @@ -0,0 +1,160 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class HttpRequestStream : ReadOnlyStream + { + private readonly IHttpBodyControlFeature _bodyControl; + private IISHttpContext _body; + private HttpStreamState _state; + private Exception _error; + + public HttpRequestStream(IHttpBodyControlFeature bodyControl) + { + _bodyControl = bodyControl; + _state = HttpStreamState.Closed; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (!_bodyControl.AllowSynchronousIO) + { + throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed); + } + + return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = ReadAsync(buffer, offset, count, default(CancellationToken), state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override int EndRead(IAsyncResult asyncResult) + { + return ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = ReadAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(task2.Result); + } + }, tcs, cancellationToken); + return tcs.Task; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateState(cancellationToken); + + return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + +#if NETCOREAPP2_1 + public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + { + ValidateState(cancellationToken); + + return ReadAsyncInternal(destination, cancellationToken); + } +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + { + try + { + return await _body.ReadAsync(buffer, cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); + } + } + + public void StartAcceptingReads(IISHttpContext body) + { + // Only start if not aborted + if (_state == HttpStreamState.Closed) + { + _state = HttpStreamState.Open; + _body = body; + } + } + + public void StopAcceptingReads() + { + // Can't use dispose (or close) as can be disposed too early by user code + // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes + _state = HttpStreamState.Closed; + _body = null; + } + + public void Abort(Exception error = null) + { + // We don't want to throw an ODE until the app func actually completes. + // If the request is aborted, we throw a TaskCanceledException instead, + // unless error is not null, in which case we throw it. + if (_state != HttpStreamState.Closed) + { + _state = HttpStreamState.Aborted; + _error = error; + } + } + + private void ValidateState(CancellationToken cancellationToken) + { + switch (_state) + { + case HttpStreamState.Open: + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + break; + case HttpStreamState.Closed: + throw new ObjectDisposedException(nameof(HttpRequestStream)); + case HttpStreamState.Aborted: + if (_error != null) + { + ExceptionDispatchInfo.Capture(_error).Throw(); + } + else + { + throw new TaskCanceledException(); + } + break; + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs new file mode 100644 index 0000000000..1a04535448 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class HttpResponseStream : WriteOnlyStream + { + private readonly IHttpBodyControlFeature _bodyControl; + private readonly IISHttpContext _context; + private HttpStreamState _state; + + public HttpResponseStream(IHttpBodyControlFeature bodyControl, IISHttpContext context) + { + _bodyControl = bodyControl; + _context = context; + _state = HttpStreamState.Closed; + } + + public override void Flush() + { + FlushAsync(default(CancellationToken)).GetAwaiter().GetResult(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + ValidateState(cancellationToken); + + return _context.FlushAsync(cancellationToken); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (!_bodyControl.AllowSynchronousIO) + { + throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed); + } + + WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult(); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + var task = WriteAsync(buffer, offset, count, default(CancellationToken), state); + if (callback != null) + { + task.ContinueWith(t => callback.Invoke(t)); + } + return task; + } + + public override void EndWrite(IAsyncResult asyncResult) + { + ((Task)asyncResult).GetAwaiter().GetResult(); + } + + private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state) + { + var tcs = new TaskCompletionSource(state); + var task = WriteAsync(buffer, offset, count, cancellationToken); + task.ContinueWith((task2, state2) => + { + var tcs2 = (TaskCompletionSource)state2; + if (task2.IsCanceled) + { + tcs2.SetCanceled(); + } + else if (task2.IsFaulted) + { + tcs2.SetException(task2.Exception); + } + else + { + tcs2.SetResult(null); + } + }, tcs, cancellationToken); + return tcs.Task; + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateState(cancellationToken); + + return _context.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken); + } + +#if NETCOREAPP2_1 + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) + { + ValidateState(cancellationToken); + + return new ValueTask(_httpResponseControl.WriteAsync(source, cancellationToken)); + } +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + public void StartAcceptingWrites() + { + // Only start if not aborted + if (_state == HttpStreamState.Closed) + { + _state = HttpStreamState.Open; + } + } + + public void StopAcceptingWrites() + { + // Can't use dispose (or close) as can be disposed too early by user code + // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes + _state = HttpStreamState.Closed; + } + + public void Abort() + { + // We don't want to throw an ODE until the app func actually completes. + if (_state != HttpStreamState.Closed) + { + _state = HttpStreamState.Aborted; + } + } + + private void ValidateState(CancellationToken cancellationToken) + { + switch (_state) + { + case HttpStreamState.Open: + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + break; + case HttpStreamState.Closed: + throw new ObjectDisposedException(nameof(HttpResponseStream), CoreStrings.WritingToResponseBodyAfterResponseCompleted); + case HttpStreamState.Aborted: + if (cancellationToken.IsCancellationRequested) + { + // Aborted state only throws on write if cancellationToken requests it + cancellationToken.ThrowIfCancellationRequested(); + } + break; + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpStreamState.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpStreamState.cs new file mode 100644 index 0000000000..231db99409 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpStreamState.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + enum HttpStreamState + { + Open, + Closed, + Aborted + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpUpgradeStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpUpgradeStream.cs new file mode 100644 index 0000000000..c8b481f948 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/HttpUpgradeStream.cs @@ -0,0 +1,208 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class HttpUpgradeStream : Stream + { + private readonly Stream _requestStream; + private readonly Stream _responseStream; + + public HttpUpgradeStream(Stream requestStream, Stream responseStream) + { + _requestStream = requestStream; + _responseStream = responseStream; + } + + public override bool CanRead + { + get + { + return _requestStream.CanRead; + } + } + + public override bool CanSeek + { + get + { + return _requestStream.CanSeek; + } + } + + public override bool CanTimeout + { + get + { + return _responseStream.CanTimeout || _requestStream.CanTimeout; + } + } + + public override bool CanWrite + { + get + { + return _responseStream.CanWrite; + } + } + + public override long Length + { + get + { + return _requestStream.Length; + } + } + + public override long Position + { + get + { + return _requestStream.Position; + } + set + { + _requestStream.Position = value; + } + } + + public override int ReadTimeout + { + get + { + return _requestStream.ReadTimeout; + } + set + { + _requestStream.ReadTimeout = value; + } + } + + public override int WriteTimeout + { + get + { + return _responseStream.WriteTimeout; + } + set + { + _responseStream.WriteTimeout = value; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _requestStream.Dispose(); + _responseStream.Dispose(); + } + } + + public override void Flush() + { + _responseStream.Flush(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return _responseStream.FlushAsync(cancellationToken); + } + + public override void Close() + { + _requestStream.Close(); + _responseStream.Close(); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _requestStream.BeginRead(buffer, offset, count, callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return _requestStream.EndRead(asyncResult); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _responseStream.BeginWrite(buffer, offset, count, callback, state); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + _responseStream.EndWrite(asyncResult); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _requestStream.ReadAsync(buffer, offset, count, cancellationToken); + } + +#if NETCOREAPP2_1 + public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + { + return _requestStream.ReadAsync(destination, cancellationToken); + } +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return _requestStream.CopyToAsync(destination, bufferSize, cancellationToken); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _responseStream.WriteAsync(buffer, offset, count, cancellationToken); + } + +#if NETCOREAPP2_1 + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) + { + return _responseStream.WriteAsync(source, cancellationToken); + } +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + public override long Seek(long offset, SeekOrigin origin) + { + return _requestStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _requestStream.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _requestStream.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return _requestStream.ReadByte(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _responseStream.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + _responseStream.WriteByte(value); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISConfigurationData.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISConfigurationData.cs new file mode 100644 index 0000000000..5c55267756 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISConfigurationData.cs @@ -0,0 +1,21 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct IISConfigurationData + { + public IntPtr pNativeApplication; + [MarshalAs(UnmanagedType.BStr)] + public string pwzFullApplicationPath; + [MarshalAs(UnmanagedType.BStr)] + public string pwzVirtualApplicationPath; + public bool fWindowsAuthEnabled; + public bool fBasicAuthEnabled; + public bool fAnonymousAuthEnable; + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.FeatureCollection.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.FeatureCollection.cs new file mode 100644 index 0000000000..6e1656a487 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.FeatureCollection.cs @@ -0,0 +1,343 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.AspNetCore.Server.IIS.Core.IO; +using Microsoft.AspNetCore.WebUtilities; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext : IFeatureCollection, + IHttpRequestFeature, + IHttpResponseFeature, + IHttpUpgradeFeature, + IHttpRequestLifetimeFeature, + IHttpAuthenticationFeature, + IServerVariablesFeature, + IHttpBufferingFeature, + ITlsConnectionFeature, + IHttpBodyControlFeature + { + // NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation, + // then the list of `implementedFeatures` in the generated code project MUST also be updated. + + private int _featureRevision; + private string _httpProtocolVersion = null; + private X509Certificate2 _certificate; + + private List> MaybeExtra; + + public void ResetFeatureCollection() + { + Initialize(); + MaybeExtra?.Clear(); + _featureRevision++; + } + + private object ExtraFeatureGet(Type key) + { + if (MaybeExtra == null) + { + return null; + } + for (var i = 0; i < MaybeExtra.Count; i++) + { + var kv = MaybeExtra[i]; + if (kv.Key == key) + { + return kv.Value; + } + } + return null; + } + + private void ExtraFeatureSet(Type key, object value) + { + if (MaybeExtra == null) + { + MaybeExtra = new List>(2); + } + + for (var i = 0; i < MaybeExtra.Count; i++) + { + if (MaybeExtra[i].Key == key) + { + MaybeExtra[i] = new KeyValuePair(key, value); + return; + } + } + MaybeExtra.Add(new KeyValuePair(key, value)); + } + + string IHttpRequestFeature.Protocol + { + get + { + if (_httpProtocolVersion == null) + { + var protocol = HttpVersion; + if (protocol.Major == 1 && protocol.Minor == 1) + { + _httpProtocolVersion = "HTTP/1.1"; + } + else if (protocol.Major == 1 && protocol.Minor == 0) + { + _httpProtocolVersion = "HTTP/1.0"; + } + else + { + _httpProtocolVersion = "HTTP/" + protocol.ToString(2); + } + } + return _httpProtocolVersion; + } + set + { + _httpProtocolVersion = value; + } + } + + string IHttpRequestFeature.Scheme + { + get => Scheme; + set => Scheme = value; + } + + string IHttpRequestFeature.Method + { + get => Method; + set => Method = value; + } + + string IHttpRequestFeature.PathBase + { + get => PathBase; + set => PathBase = value; + } + + string IHttpRequestFeature.Path + { + get => Path; + set => Path = value; + } + + string IHttpRequestFeature.QueryString + { + get => QueryString; + set => QueryString = value; + } + + string IHttpRequestFeature.RawTarget + { + get => RawTarget; + set => RawTarget = value; + } + + IHeaderDictionary IHttpRequestFeature.Headers + { + get => RequestHeaders; + set => RequestHeaders = value; + } + + Stream IHttpRequestFeature.Body + { + get => RequestBody; + set => RequestBody = value; + } + + int IHttpResponseFeature.StatusCode + { + get => StatusCode; + set => StatusCode = value; + } + + string IHttpResponseFeature.ReasonPhrase + { + get => ReasonPhrase; + set => ReasonPhrase = value; + } + + IHeaderDictionary IHttpResponseFeature.Headers + { + get => ResponseHeaders; + set => ResponseHeaders = value; + } + + Stream IHttpResponseFeature.Body + { + get => ResponseBody; + set => ResponseBody = value; + } + + bool IHttpResponseFeature.HasStarted => HasResponseStarted; + + bool IHttpUpgradeFeature.IsUpgradableRequest => true; + + bool IFeatureCollection.IsReadOnly => false; + + int IFeatureCollection.Revision => _featureRevision; + + ClaimsPrincipal IHttpAuthenticationFeature.User + { + get => User; + set => User = value; + } + + public IAuthenticationHandler Handler { get; set; } + + string IServerVariablesFeature.this[string variableName] + { + get + { + if (string.IsNullOrEmpty(variableName)) + { + throw new ArgumentException($"{nameof(variableName)} should be non-empty string"); + } + + // Synchronize access to native methods that might run in parallel with IO loops + lock (_contextLock) + { + return NativeMethods.HttpTryGetServerVariable(_pInProcessHandler, variableName, out var value) ? value : null; + } + } + set + { + if (string.IsNullOrEmpty(variableName)) + { + throw new ArgumentException($"{nameof(variableName)} should be non-empty string"); + } + + // Synchronize access to native methods that might run in parallel with IO loops + lock (_contextLock) + { + NativeMethods.HttpSetServerVariable(_pInProcessHandler, variableName, value); + } + } + } + + object IFeatureCollection.this[Type key] + { + get => FastFeatureGet(key); + set => FastFeatureSet(key, value); + } + + TFeature IFeatureCollection.Get() + { + return (TFeature)FastFeatureGet(typeof(TFeature)); + } + + void IFeatureCollection.Set(TFeature instance) + { + FastFeatureSet(typeof(TFeature), instance); + } + + void IHttpResponseFeature.OnStarting(Func callback, object state) + { + OnStarting(callback, state); + } + + void IHttpResponseFeature.OnCompleted(Func callback, object state) + { + OnCompleted(callback, state); + } + + async Task IHttpUpgradeFeature.UpgradeAsync() + { + if (!((IHttpUpgradeFeature)this).IsUpgradableRequest) + { + throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest); + } + + if (_wasUpgraded) + { + throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes); + } + if (HasResponseStarted) + { + throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes); + } + + _wasUpgraded = true; + + StatusCode = StatusCodes.Status101SwitchingProtocols; + ReasonPhrase = ReasonPhrases.GetReasonPhrase(StatusCodes.Status101SwitchingProtocols); + + // If we started reading before calling Upgrade Task should be completed at this point + // because read would return 0 synchronously + Debug.Assert(_readBodyTask == null || _readBodyTask.IsCompleted); + + // Reset reading status to allow restarting with new IO + _hasRequestReadingStarted = false; + + // Upgrade async will cause the stream processing to go into duplex mode + AsyncIO = new WebSocketsAsyncIOEngine(_contextLock, _pInProcessHandler); + + await InitializeResponse(flushHeaders: true); + + return _streams.Upgrade(); + } + + Task ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken) + { + return Task.FromResult(((ITlsConnectionFeature)this).ClientCertificate); + } + + unsafe X509Certificate2 ITlsConnectionFeature.ClientCertificate + { + get + { + if (_certificate == null && + NativeRequest->pSslInfo != null && + NativeRequest->pSslInfo->pClientCertInfo != null && + NativeRequest->pSslInfo->pClientCertInfo->pCertEncoded != null && + NativeRequest->pSslInfo->pClientCertInfo->CertEncodedSize != 0) + { + // Based off of from https://referencesource.microsoft.com/#system/net/System/Net/HttpListenerRequest.cs,1037c8ec82879ba0,references + var rawCertificateCopy = new byte[NativeRequest->pSslInfo->pClientCertInfo->CertEncodedSize]; + Marshal.Copy((IntPtr)NativeRequest->pSslInfo->pClientCertInfo->pCertEncoded, rawCertificateCopy, 0, rawCertificateCopy.Length); + _certificate = new X509Certificate2(rawCertificateCopy); + } + + return _certificate; + } + set + { + _certificate = value; + } + } + + IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); + + bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true; + + void IHttpBufferingFeature.DisableRequestBuffering() + { + } + + void IHttpBufferingFeature.DisableResponseBuffering() + { + NativeMethods.HttpDisableBuffering(_pInProcessHandler); + DisableCompression(); + } + + private void DisableCompression() + { + var serverVariableFeature = (IServerVariablesFeature)this; + serverVariableFeature["IIS_EnableDynamicCompression"] = "0"; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Features.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Features.cs new file mode 100644 index 0000000000..6e107e03f0 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Features.cs @@ -0,0 +1,347 @@ +// 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; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext + { + private static readonly Type IHttpRequestFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); + private static readonly Type IHttpResponseFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); + private static readonly Type IHttpRequestIdentifierFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); + private static readonly Type IServiceProvidersFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); + private static readonly Type IHttpRequestLifetimeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); + private static readonly Type IHttpConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); + private static readonly Type IHttpAuthenticationFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); + private static readonly Type IQueryFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IQueryFeature); + private static readonly Type IFormFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IFormFeature); + private static readonly Type IHttpUpgradeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); + private static readonly Type IResponseCookiesFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); + private static readonly Type IItemsFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IItemsFeature); + private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); + private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); + private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature); + private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); + private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); + private static readonly Type IISHttpContextType = typeof(IISHttpContext); + private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature); + private static readonly Type IHttpBufferingFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature); + + private object _currentIHttpRequestFeature; + private object _currentIHttpResponseFeature; + private object _currentIHttpRequestIdentifierFeature; + private object _currentIServiceProvidersFeature; + private object _currentIHttpRequestLifetimeFeature; + private object _currentIHttpConnectionFeature; + private object _currentIHttpAuthenticationFeature; + private object _currentIQueryFeature; + private object _currentIFormFeature; + private object _currentIHttpUpgradeFeature; + private object _currentIResponseCookiesFeature; + private object _currentIItemsFeature; + private object _currentITlsConnectionFeature; + private object _currentIHttpWebSocketFeature; + private object _currentISessionFeature; + private object _currentIHttpBodyControlFeature; + private object _currentIHttpSendFileFeature; + private object _currentIServerVariablesFeature; + private object _currentIHttpBufferingFeature; + + private void Initialize() + { + _currentIHttpRequestFeature = this; + _currentIHttpResponseFeature = this; + _currentIHttpUpgradeFeature = this; + _currentIHttpRequestIdentifierFeature = this; + _currentIHttpRequestLifetimeFeature = this; + _currentIHttpConnectionFeature = this; + _currentIHttpBodyControlFeature = this; + _currentIHttpAuthenticationFeature = this; + _currentIServerVariablesFeature = this; + _currentIHttpBufferingFeature = this; + _currentITlsConnectionFeature = this; + } + + internal object FastFeatureGet(Type key) + { + if (key == IHttpRequestFeatureType) + { + return _currentIHttpRequestFeature; + } + if (key == IHttpResponseFeatureType) + { + return _currentIHttpResponseFeature; + } + if (key == IHttpRequestIdentifierFeatureType) + { + return _currentIHttpRequestIdentifierFeature; + } + if (key == IServiceProvidersFeatureType) + { + return _currentIServiceProvidersFeature; + } + if (key == IHttpRequestLifetimeFeatureType) + { + return _currentIHttpRequestLifetimeFeature; + } + if (key == IHttpConnectionFeatureType) + { + return _currentIHttpConnectionFeature; + } + if (key == IHttpAuthenticationFeatureType) + { + return _currentIHttpAuthenticationFeature; + } + if (key == IQueryFeatureType) + { + return _currentIQueryFeature; + } + if (key == IFormFeatureType) + { + return _currentIFormFeature; + } + if (key == IHttpUpgradeFeatureType) + { + return _currentIHttpUpgradeFeature; + } + if (key == IResponseCookiesFeatureType) + { + return _currentIResponseCookiesFeature; + } + if (key == IItemsFeatureType) + { + return _currentIItemsFeature; + } + if (key == ITlsConnectionFeatureType) + { + return _currentITlsConnectionFeature; + } + if (key == IHttpWebSocketFeatureType) + { + return _currentIHttpWebSocketFeature; + } + if (key == ISessionFeatureType) + { + return _currentISessionFeature; + } + if (key == IHttpBodyControlFeatureType) + { + return _currentIHttpBodyControlFeature; + } + if (key == IHttpSendFileFeatureType) + { + return _currentIHttpSendFileFeature; + } + if (key == IISHttpContextType) + { + return this; + } + if (key == IServerVariablesFeature) + { + return _currentIServerVariablesFeature; + } + if (key == IHttpBufferingFeature) + { + return _currentIHttpBufferingFeature; + } + + return ExtraFeatureGet(key); + } + + internal void FastFeatureSet(Type key, object feature) + { + _featureRevision++; + + if (key == IHttpRequestFeatureType) + { + _currentIHttpRequestFeature = feature; + return; + } + if (key == IHttpResponseFeatureType) + { + _currentIHttpResponseFeature = feature; + return; + } + if (key == IHttpRequestIdentifierFeatureType) + { + _currentIHttpRequestIdentifierFeature = feature; + return; + } + if (key == IServiceProvidersFeatureType) + { + _currentIServiceProvidersFeature = feature; + return; + } + if (key == IHttpRequestLifetimeFeatureType) + { + _currentIHttpRequestLifetimeFeature = feature; + return; + } + if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = feature; + return; + } + if (key == IHttpAuthenticationFeatureType) + { + _currentIHttpAuthenticationFeature = feature; + return; + } + if (key == IQueryFeatureType) + { + _currentIQueryFeature = feature; + return; + } + if (key == IFormFeatureType) + { + _currentIFormFeature = feature; + return; + } + if (key == IHttpUpgradeFeatureType) + { + _currentIHttpUpgradeFeature = feature; + return; + } + if (key == IResponseCookiesFeatureType) + { + _currentIResponseCookiesFeature = feature; + return; + } + if (key == IItemsFeatureType) + { + _currentIItemsFeature = feature; + return; + } + if (key == ITlsConnectionFeatureType) + { + _currentITlsConnectionFeature = feature; + return; + } + if (key == IHttpWebSocketFeatureType) + { + _currentIHttpWebSocketFeature = feature; + return; + } + if (key == ISessionFeatureType) + { + _currentISessionFeature = feature; + return; + } + if (key == IHttpBodyControlFeatureType) + { + _currentIHttpBodyControlFeature = feature; + return; + } + if (key == IHttpSendFileFeatureType) + { + _currentIHttpSendFileFeature = feature; + return; + } + if (key == IServerVariablesFeature) + { + _currentIServerVariablesFeature = feature; + return; + } + if (key == IHttpBufferingFeature) + { + _currentIHttpBufferingFeature = feature; + return; + } + if (key == IISHttpContextType) + { + throw new InvalidOperationException("Cannot set IISHttpContext in feature collection"); + }; + ExtraFeatureSet(key, feature); + } + + private IEnumerable> FastEnumerable() + { + if (_currentIHttpRequestFeature != null) + { + yield return new KeyValuePair(IHttpRequestFeatureType, _currentIHttpRequestFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); + } + if (_currentIHttpResponseFeature != null) + { + yield return new KeyValuePair(IHttpResponseFeatureType, _currentIHttpResponseFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); + } + if (_currentIHttpRequestIdentifierFeature != null) + { + yield return new KeyValuePair(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); + } + if (_currentIServiceProvidersFeature != null) + { + yield return new KeyValuePair(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); + } + if (_currentIHttpRequestLifetimeFeature != null) + { + yield return new KeyValuePair(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); + } + if (_currentIHttpConnectionFeature != null) + { + yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); + } + if (_currentIHttpAuthenticationFeature != null) + { + yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); + } + if (_currentIQueryFeature != null) + { + yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature as global::Microsoft.AspNetCore.Http.Features.IQueryFeature); + } + if (_currentIFormFeature != null) + { + yield return new KeyValuePair(IFormFeatureType, _currentIFormFeature as global::Microsoft.AspNetCore.Http.Features.IFormFeature); + } + if (_currentIHttpUpgradeFeature != null) + { + yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); + } + if (_currentIResponseCookiesFeature != null) + { + yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); + } + if (_currentIItemsFeature != null) + { + yield return new KeyValuePair(IItemsFeatureType, _currentIItemsFeature as global::Microsoft.AspNetCore.Http.Features.IItemsFeature); + } + if (_currentITlsConnectionFeature != null) + { + yield return new KeyValuePair(ITlsConnectionFeatureType, _currentITlsConnectionFeature as global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); + } + if (_currentIHttpWebSocketFeature != null) + { + yield return new KeyValuePair(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); + } + if (_currentISessionFeature != null) + { + yield return new KeyValuePair(ISessionFeatureType, _currentISessionFeature as global::Microsoft.AspNetCore.Http.Features.ISessionFeature); + } + if (_currentIHttpBodyControlFeature != null) + { + yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); + } + if (_currentIHttpSendFileFeature != null) + { + yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); + } + if (_currentIServerVariablesFeature != null) + { + yield return new KeyValuePair(IServerVariablesFeature, _currentIServerVariablesFeature as global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature); + } + if (_currentIHttpBufferingFeature != null) + { + yield return new KeyValuePair(IHttpBufferingFeature, _currentIHttpBufferingFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature); + } + + if (MaybeExtra != null) + { + foreach (var item in MaybeExtra) + { + yield return item; + } + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpConnectionFeature.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpConnectionFeature.cs new file mode 100644 index 0000000000..40b70a2b90 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpConnectionFeature.cs @@ -0,0 +1,100 @@ +// 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.Globalization; +using System.Net; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext : IHttpConnectionFeature + { + IPAddress IHttpConnectionFeature.RemoteIpAddress + { + get + { + if (RemoteIpAddress == null) + { + InitializeRemoteEndpoint(); + } + + return RemoteIpAddress; + } + set => RemoteIpAddress = value; + } + + IPAddress IHttpConnectionFeature.LocalIpAddress + { + get + { + if (LocalIpAddress == null) + { + InitializeLocalEndpoint(); + } + return LocalIpAddress; + } + set => LocalIpAddress = value; + } + + int IHttpConnectionFeature.RemotePort + { + get + { + if (RemoteIpAddress == null) + { + InitializeRemoteEndpoint(); + } + + return RemotePort; + } + set => RemotePort = value; + } + + int IHttpConnectionFeature.LocalPort + { + get + { + if (LocalIpAddress == null) + { + InitializeLocalEndpoint(); + } + + return LocalPort; + } + set => LocalPort = value; + } + + string IHttpConnectionFeature.ConnectionId + { + get + { + if (RequestConnectionId == null) + { + InitializeConnectionId(); + } + + return RequestConnectionId; + } + set => RequestConnectionId = value; + } + + private void InitializeLocalEndpoint() + { + var localEndPoint = GetLocalEndPoint(); + LocalIpAddress = localEndPoint.GetIPAddress(); + LocalPort = localEndPoint.GetPort(); + } + + private void InitializeRemoteEndpoint() + { + var remoteEndPoint = GetRemoteEndPoint(); + RemoteIpAddress = remoteEndPoint.GetIPAddress(); + RemotePort = remoteEndPoint.GetPort(); + } + + private void InitializeConnectionId() + { + RequestConnectionId = ConnectionId.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestIdentifierFeature.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestIdentifierFeature.cs new file mode 100644 index 0000000000..2d735a7d8f --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestIdentifierFeature.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext : IHttpRequestIdentifierFeature + { + string IHttpRequestIdentifierFeature.TraceIdentifier + { + get + { + if (TraceIdentifier == null) + { + InitializeHttpRequestIdentifierFeature(); + } + + return TraceIdentifier; + } + set => TraceIdentifier = value; + } + + private unsafe void InitializeHttpRequestIdentifierFeature() + { + // Copied from WebListener + // This is the base GUID used by HTTP.SYS for generating the activity ID. + // HTTP.SYS overwrites the first 8 bytes of the base GUID with RequestId to generate ETW activity ID. + // The requestId should be set by the NativeRequestContext + var guid = new Guid(0xffcb4c93, 0xa57f, 0x453c, 0xb6, 0x3f, 0x84, 0x71, 0xc, 0x79, 0x67, 0xbb); + *((ulong*)&guid) = RequestId; + + // TODO: Also make this not slow + TraceIdentifier = guid.ToString(); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestLifetimeFeature.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestLifetimeFeature.cs new file mode 100644 index 0000000000..ab46f8df5c --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IHttpRequestLifetimeFeature.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext : IHttpRequestLifetimeFeature + { + private CancellationTokenSource _abortedCts; + private CancellationToken? _manuallySetRequestAbortToken; + + CancellationToken IHttpRequestLifetimeFeature.RequestAborted + { + get + { + // If a request abort token was previously explicitly set, return it. + if (_manuallySetRequestAbortToken.HasValue) + { + return _manuallySetRequestAbortToken.Value; + } + // Otherwise, get the abort CTS. If we have one, which would mean that someone previously + // asked for the RequestAborted token, simply return its token. If we don't, + // check to see whether we've already aborted, in which case just return an + // already canceled token. Finally, force a source into existence if we still + // don't have one, and return its token. + var cts = _abortedCts; + return + cts != null ? cts.Token : + (_requestAborted == 1) ? new CancellationToken(true) : + RequestAbortedSource.Token; + } + set + { + // Set an abort token, overriding one we create internally. This setter and associated + // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted. + _manuallySetRequestAbortToken = value; + } + } + + private CancellationTokenSource RequestAbortedSource + { + get + { + // Get the abort token, lazily-initializing it if necessary. + // Make sure it's canceled if an abort request already came in. + + // EnsureInitialized can return null since _abortedCts is reset to null + // after it's already been initialized to a non-null value. + // If EnsureInitialized does return null, this property was accessed between + // requests so it's safe to return an ephemeral CancellationTokenSource. + var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource()) + ?? new CancellationTokenSource(); + + if (_requestAborted == 1) + { + cts.Cancel(); + } + return cts; + } + } + + void IHttpRequestLifetimeFeature.Abort() + { + Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication)); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IO.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IO.cs new file mode 100644 index 0000000000..e945500672 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.IO.cs @@ -0,0 +1,232 @@ +// 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.Buffers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal partial class IISHttpContext + { + /// + /// Reads data from the Input pipe to the user. + /// + /// + /// + /// + internal async Task ReadAsync(Memory memory, CancellationToken cancellationToken) + { + if (!_hasRequestReadingStarted) + { + InitializeRequestIO(); + } + + while (true) + { + var result = await _bodyInputPipe.Reader.ReadAsync(cancellationToken); + var readableBuffer = result.Buffer; + try + { + if (!readableBuffer.IsEmpty) + { + var actual = Math.Min(readableBuffer.Length, memory.Length); + readableBuffer = readableBuffer.Slice(0, actual); + readableBuffer.CopyTo(memory.Span); + return (int)actual; + } + else if (result.IsCompleted) + { + return 0; + } + } + finally + { + _bodyInputPipe.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End); + } + } + } + + /// + /// Writes data to the output pipe. + /// + /// + /// + /// + internal Task WriteAsync(ReadOnlyMemory memory, CancellationToken cancellationToken = default(CancellationToken)) + { + async Task WriteFirstAsync() + { + await InitializeResponse(flushHeaders: false); + await _bodyOutput.WriteAsync(memory, cancellationToken); + } + + return !HasResponseStarted ? WriteFirstAsync() : _bodyOutput.WriteAsync(memory, cancellationToken); + } + + /// + /// Flushes the data in the output pipe + /// + /// + /// + internal Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + async Task FlushFirstAsync() + { + await InitializeResponse(flushHeaders: true); + await _bodyOutput.FlushAsync(cancellationToken); + } + + return !HasResponseStarted ? FlushFirstAsync() : _bodyOutput.FlushAsync(cancellationToken); + } + + private async Task ReadBody() + { + Exception error = null; + try + { + while (true) + { + var memory = _bodyInputPipe.Writer.GetMemory(); + + var read = await AsyncIO.ReadAsync(memory); + + // End of body + if (read == 0) + { + break; + } + + // Read was not canceled because of incoming write or IO stopping + if (read != -1) + { + _bodyInputPipe.Writer.Advance(read); + } + + var result = await _bodyInputPipe.Writer.FlushAsync(); + + if (result.IsCompleted || result.IsCanceled) + { + break; + } + } + } + catch (ConnectionResetException ex) + { + ConnectionReset(); + error = ex; + } + catch (Exception ex) + { + error = ex; + Log.UnexpectedError(_logger, nameof(IISHttpContext), ex); + } + finally + { + _bodyInputPipe.Writer.Complete(error); + } + } + + private async Task WriteBody(bool flush = false) + { + Exception error = null; + try + { + while (true) + { + var result = await _bodyOutput.Reader.ReadAsync(); + + var buffer = result.Buffer; + + try + { + if (!buffer.IsEmpty) + { + await AsyncIO.WriteAsync(buffer); + } + + // if request is done no need to flush, http.sys would do it for us + if (result.IsCompleted) + { + break; + } + + flush = flush | result.IsCanceled; + + if (flush) + { + await AsyncIO.FlushAsync(); + flush = false; + } + } + finally + { + _bodyOutput.Reader.AdvanceTo(buffer.End); + } + } + } + // We want to swallow IO exception and allow app to finish writing + catch (ConnectionResetException) + { + ConnectionReset(); + } + catch (Exception ex) + { + error = ex; + Log.UnexpectedError(_logger, nameof(IISHttpContext), ex); + } + finally + { + _bodyOutput.Reader.Complete(error); + } + } + + private bool AbortIO() + { + if (Interlocked.CompareExchange(ref _requestAborted, 1, 0) != 0) + { + return false; + } + + _bodyOutput.Dispose(); + + var cts = _abortedCts; + if (cts != null) + { + ThreadPool.QueueUserWorkItem(t => + { + try + { + cts.Cancel(); + } + catch (Exception ex) + { + Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex); + } + }); + } + + return true; + } + + public void Abort(Exception reason) + { + _bodyOutput.Abort(reason); + _streams.Abort(reason); + NativeMethods.HttpCloseConnection(_pInProcessHandler); + + AbortIO(); + } + + internal void ConnectionReset() + { + if (AbortIO()) + { + Log.ConnectionDisconnect(_logger, ((IHttpConnectionFeature)this).ConnectionId); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Log.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Log.cs new file mode 100644 index 0000000000..1056ac1479 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.Log.cs @@ -0,0 +1,39 @@ +// 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.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal abstract partial class IISHttpContext + { + private static class Log + { + private static readonly Action _connectionDisconnect = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "ConnectionDisconnect"), @"Connection ID ""{ConnectionId}"" disconnecting."); + + private static readonly Action _applicationError = + LoggerMessage.Define(LogLevel.Error, new EventId(2, "ApplicationError"), @"Connection ID ""{ConnectionId}"", Request ID ""{TraceIdentifier}"": An unhandled exception was thrown by the application."); + + private static readonly Action _unexpectedError = + LoggerMessage.Define(LogLevel.Error, new EventId(3, "UnexpectedError"), @"Unexpected exception in ""{ClassName}.{MethodName}""."); + + public static void ConnectionDisconnect(ILogger logger, string connectionId) + { + _connectionDisconnect(logger, connectionId, null); + } + + public static void ApplicationError(ILogger logger, string connectionId, string traceIdentifier, Exception ex) + { + _applicationError(logger, connectionId, traceIdentifier, ex); + } + + public static void UnexpectedError(ILogger logger, string className, Exception ex, [CallerMemberName] string methodName = null) + { + _unexpectedError(logger, className, methodName, ex); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs new file mode 100644 index 0000000000..09e478a989 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs @@ -0,0 +1,522 @@ +// 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.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipelines; +using System.Net; +using System.Runtime.InteropServices; +using System.Security.Claims; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpSys.Internal; +using Microsoft.AspNetCore.Server.IIS.Core.IO; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal abstract partial class IISHttpContext : NativeRequestContext, IDisposable + { + private const int MinAllocBufferSize = 2048; + private const int PauseWriterThreshold = 65536; + private const int ResumeWriterTheshold = PauseWriterThreshold / 2; + + protected readonly IntPtr _pInProcessHandler; + + private readonly IISServerOptions _options; + + protected Streams _streams; + + private volatile bool _hasResponseStarted; + private volatile bool _hasRequestReadingStarted; + + private int _statusCode; + private string _reasonPhrase; + // Used to synchronize callback registration and native method calls + private readonly object _contextLock = new object(); + + protected Stack, object>> _onStarting; + protected Stack, object>> _onCompleted; + + protected Exception _applicationException; + private readonly MemoryPool _memoryPool; + private readonly IISHttpServer _server; + + private readonly ILogger _logger; + + private GCHandle _thisHandle; + protected Task _readBodyTask; + protected Task _writeBodyTask; + + private bool _wasUpgraded; + protected int _requestAborted; + + protected Pipe _bodyInputPipe; + protected OutputProducer _bodyOutput; + + private const string NtlmString = "NTLM"; + private const string NegotiateString = "Negotiate"; + private const string BasicString = "Basic"; + + + internal unsafe IISHttpContext( + MemoryPool memoryPool, + IntPtr pInProcessHandler, + IISServerOptions options, + IISHttpServer server, + ILogger logger) + : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler)) + { + _memoryPool = memoryPool; + _pInProcessHandler = pInProcessHandler; + _options = options; + _server = server; + _logger = logger; + } + + public Version HttpVersion { get; set; } + public string Scheme { get; set; } + public string Method { get; set; } + public string PathBase { get; set; } + public string Path { get; set; } + public string QueryString { get; set; } + public string RawTarget { get; set; } + + public bool HasResponseStarted => _hasResponseStarted; + public IPAddress RemoteIpAddress { get; set; } + public int RemotePort { get; set; } + public IPAddress LocalIpAddress { get; set; } + public int LocalPort { get; set; } + public string RequestConnectionId { get; set; } + public string TraceIdentifier { get; set; } + public ClaimsPrincipal User { get; set; } + internal WindowsPrincipal WindowsUser { get; set; } + public Stream RequestBody { get; set; } + public Stream ResponseBody { get; set; } + + protected IAsyncIOEngine AsyncIO { get; set; } + + public IHeaderDictionary RequestHeaders { get; set; } + public IHeaderDictionary ResponseHeaders { get; set; } + private HeaderCollection HttpResponseHeaders { get; set; } + internal HttpApiTypes.HTTP_VERB KnownMethod { get; private set; } + + protected void InitializeContext() + { + _thisHandle = GCHandle.Alloc(this); + + Method = GetVerb(); + + RawTarget = GetRawUrl(); + // TODO version is slow. + HttpVersion = GetVersion(); + Scheme = SslStatus != SslStatus.Insecure ? Constants.HttpsScheme : Constants.HttpScheme; + KnownMethod = VerbId; + StatusCode = 200; + + var originalPath = GetOriginalPath(); + + if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawTarget, "*", StringComparison.Ordinal)) + { + PathBase = string.Empty; + Path = string.Empty; + } + else + { + // Path and pathbase are unescaped by RequestUriBuilder + // The UsePathBase middleware will modify the pathbase and path correctly + PathBase = string.Empty; + Path = originalPath; + } + + var cookedUrl = GetCookedUrl(); + QueryString = cookedUrl.GetQueryString() ?? string.Empty; + + RequestHeaders = new RequestHeaders(this); + HttpResponseHeaders = new HeaderCollection(); + ResponseHeaders = HttpResponseHeaders; + + if (_options.ForwardWindowsAuthentication) + { + WindowsUser = GetWindowsPrincipal(); + if (_options.AutomaticAuthentication) + { + User = WindowsUser; + } + } + + ResetFeatureCollection(); + + if (!_server.IsWebSocketAvailable(_pInProcessHandler)) + { + _currentIHttpUpgradeFeature = null; + } + + _streams = new Streams(this); + + (RequestBody, ResponseBody) = _streams.Start(); + + var pipe = new Pipe( + new PipeOptions( + _memoryPool, + readerScheduler: PipeScheduler.ThreadPool, + pauseWriterThreshold: PauseWriterThreshold, + resumeWriterThreshold: ResumeWriterTheshold, + minimumSegmentSize: MinAllocBufferSize)); + _bodyOutput = new OutputProducer(pipe); + + NativeMethods.HttpSetManagedContext(_pInProcessHandler, (IntPtr)_thisHandle); + } + + private string GetOriginalPath() + { + // applicationInitialization request might have trailing \0 character included in the length + // check and skip it + var rawUrlInBytes = GetRawUrlInBytes(); + if (rawUrlInBytes.Length > 0 && rawUrlInBytes[rawUrlInBytes.Length - 1] == 0) + { + var newRawUrlInBytes = new byte[rawUrlInBytes.Length - 1]; + Array.Copy(rawUrlInBytes, newRawUrlInBytes, newRawUrlInBytes.Length); + rawUrlInBytes = newRawUrlInBytes; + } + + var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes); + return originalPath; + } + + public int StatusCode + { + get { return _statusCode; } + set + { + if (HasResponseStarted) + { + ThrowResponseAlreadyStartedException(nameof(StatusCode)); + } + _statusCode = (ushort)value; + } + } + + public string ReasonPhrase + { + get { return _reasonPhrase; } + set + { + if (HasResponseStarted) + { + ThrowResponseAlreadyStartedException(nameof(ReasonPhrase)); + } + _reasonPhrase = value; + } + } + + internal IISHttpServer Server => _server; + + private async Task InitializeResponse(bool flushHeaders) + { + await FireOnStarting(); + + if (_applicationException != null) + { + ThrowResponseAbortedException(); + } + + await ProduceStart(flushHeaders); + } + + private async Task ProduceStart(bool flushHeaders) + { + Debug.Assert(_hasResponseStarted == false); + + _hasResponseStarted = true; + + SetResponseHeaders(); + + EnsureIOInitialized(); + + if (flushHeaders) + { + try + { + await AsyncIO.FlushAsync(); + } + // Client might be disconnected at this point + // don't leak the exception + catch (ConnectionResetException) + { + ConnectionReset(); + } + } + + _writeBodyTask = WriteBody(!flushHeaders); + } + + private void InitializeRequestIO() + { + Debug.Assert(!_hasRequestReadingStarted); + + _hasRequestReadingStarted = true; + + EnsureIOInitialized(); + + _bodyInputPipe = new Pipe(new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.ThreadPool, minimumSegmentSize: MinAllocBufferSize)); + _readBodyTask = ReadBody(); + } + + private void EnsureIOInitialized() + { + // If at this point request was not upgraded just start a normal IO engine + if (AsyncIO == null) + { + AsyncIO = new AsyncIOEngine(_contextLock, _pInProcessHandler); + } + } + + private void ThrowResponseAbortedException() + { + throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException); + } + + protected Task ProduceEnd() + { + if (_applicationException != null) + { + if (HasResponseStarted) + { + // We can no longer change the response, so we simply close the connection. + return Task.CompletedTask; + } + + // If the request was rejected, the error state has already been set by SetBadRequestState and + // that should take precedence. + else + { + // 500 Internal Server Error + SetErrorResponseHeaders(statusCode: StatusCodes.Status500InternalServerError); + } + } + + if (!HasResponseStarted) + { + return ProduceEndAwaited(); + } + + return Task.CompletedTask; + } + + private void SetErrorResponseHeaders(int statusCode) + { + StatusCode = statusCode; + ReasonPhrase = string.Empty; + HttpResponseHeaders.Clear(); + } + + private async Task ProduceEndAwaited() + { + await ProduceStart(flushHeaders: true); + await _bodyOutput.FlushAsync(default); + } + + public unsafe void SetResponseHeaders() + { + // Verifies we have sent the statuscode before writing a header + var reasonPhrase = string.IsNullOrEmpty(ReasonPhrase) ? ReasonPhrases.GetReasonPhrase(StatusCode) : ReasonPhrase; + + // This copies data into the underlying buffer + NativeMethods.HttpSetResponseStatusCode(_pInProcessHandler, (ushort)StatusCode, reasonPhrase); + + HttpResponseHeaders.IsReadOnly = true; + foreach (var headerPair in HttpResponseHeaders) + { + var headerValues = headerPair.Value; + var knownHeaderIndex = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key); + for (var i = 0; i < headerValues.Count; i++) + { + var isFirst = i == 0; + var headerValueBytes = Encoding.UTF8.GetBytes(headerValues[i]); + fixed (byte* pHeaderValue = headerValueBytes) + { + if (knownHeaderIndex == -1) + { + var headerNameBytes = Encoding.UTF8.GetBytes(headerPair.Key); + fixed (byte* pHeaderName = headerNameBytes) + { + NativeMethods.HttpResponseSetUnknownHeader(_pInProcessHandler, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst); + } + } + else + { + NativeMethods.HttpResponseSetKnownHeader(_pInProcessHandler, knownHeaderIndex, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst); + } + } + } + } + } + + public abstract Task ProcessRequestAsync(); + + public void OnStarting(Func callback, object state) + { + lock (_contextLock) + { + if (HasResponseStarted) + { + throw new InvalidOperationException("Response already started"); + } + + if (_onStarting == null) + { + _onStarting = new Stack, object>>(); + } + _onStarting.Push(new KeyValuePair, object>(callback, state)); + } + } + + public void OnCompleted(Func callback, object state) + { + lock (_contextLock) + { + if (_onCompleted == null) + { + _onCompleted = new Stack, object>>(); + } + _onCompleted.Push(new KeyValuePair, object>(callback, state)); + } + } + + protected async Task FireOnStarting() + { + Stack, object>> onStarting = null; + lock (_contextLock) + { + onStarting = _onStarting; + _onStarting = null; + } + if (onStarting != null) + { + try + { + foreach (var entry in onStarting) + { + await entry.Key.Invoke(entry.Value); + } + } + catch (Exception ex) + { + ReportApplicationError(ex); + } + } + } + + protected async Task FireOnCompleted() + { + Stack, object>> onCompleted = null; + lock (_contextLock) + { + onCompleted = _onCompleted; + _onCompleted = null; + } + if (onCompleted != null) + { + foreach (var entry in onCompleted) + { + try + { + await entry.Key.Invoke(entry.Value); + } + catch (Exception ex) + { + ReportApplicationError(ex); + } + } + } + } + + protected void ReportApplicationError(Exception ex) + { + if (_applicationException == null) + { + _applicationException = ex; + } + else if (_applicationException is AggregateException) + { + _applicationException = new AggregateException(_applicationException, ex).Flatten(); + } + else + { + _applicationException = new AggregateException(_applicationException, ex); + } + + Log.ApplicationError(_logger, ((IHttpConnectionFeature)this).ConnectionId, TraceIdentifier, ex); + } + + public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus) + { + NativeMethods.HttpSetCompletionStatus(_pInProcessHandler, requestNotificationStatus); + NativeMethods.HttpPostCompletion(_pInProcessHandler, 0); + } + + internal void OnAsyncCompletion(int hr, int bytes) + { + AsyncIO.NotifyCompletion(hr, bytes); + } + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _thisHandle.Free(); + } + + if (WindowsUser?.Identity is WindowsIdentity wi) + { + wi.Dispose(); + } + + _abortedCts?.Dispose(); + + disposedValue = true; + } + } + + public override void Dispose() + { + Dispose(disposing: true); + } + + private void ThrowResponseAlreadyStartedException(string name) + { + throw new InvalidOperationException(CoreStrings.FormatParameterReadOnlyAfterResponseStarted(name)); + } + + private WindowsPrincipal GetWindowsPrincipal() + { + NativeMethods.HttpGetAuthenticationInformation(_pInProcessHandler, out var authenticationType, out var token); + + if (token != IntPtr.Zero && authenticationType != null) + { + if ((authenticationType.Equals(NtlmString, StringComparison.OrdinalIgnoreCase) + || authenticationType.Equals(NegotiateString, StringComparison.OrdinalIgnoreCase) + || authenticationType.Equals(BasicString, StringComparison.OrdinalIgnoreCase))) + { + return new WindowsPrincipal(new WindowsIdentity(token, authenticationType)); + } + } + return null; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs new file mode 100644 index 0000000000..bb104f5ac1 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContextOfT.cs @@ -0,0 +1,107 @@ +// 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.Buffers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class IISHttpContextOfT : IISHttpContext + { + private readonly IHttpApplication _application; + + public IISHttpContextOfT(MemoryPool memoryPool, IHttpApplication application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger) + : base(memoryPool, pInProcessHandler, options, server, logger) + { + _application = application; + } + + public override async Task ProcessRequestAsync() + { + InitializeContext(); + + var context = default(TContext); + var success = true; + + try + { + context = _application.CreateContext(this); + await _application.ProcessRequestAsync(context); + // TODO Verification of Response + //if (Volatile.Read(ref _requestAborted) == 0) + //{ + // VerifyResponseContentLength(); + //} + } + catch (Exception ex) + { + ReportApplicationError(ex); + success = false; + } + finally + { + _streams.Stop(); + + if (!HasResponseStarted && _applicationException == null && _onStarting != null) + { + await FireOnStarting(); + // Dispose + } + + if (_onCompleted != null) + { + await FireOnCompleted(); + } + } + + if (Volatile.Read(ref _requestAborted) == 0) + { + await ProduceEnd(); + } + else if (!HasResponseStarted) + { + // If the request was aborted and no response was sent, there's no + // meaningful status code to log. + StatusCode = 0; + success = false; + } + + try + { + _application.DisposeContext(context, _applicationException); + } + catch (Exception ex) + { + // TODO Log this + _applicationException = _applicationException ?? ex; + success = false; + } + finally + { + // Complete response writer and request reader pipe sides + _bodyOutput.Dispose(); + _bodyInputPipe?.Reader.Complete(); + + // Allow writes to drain + if (_writeBodyTask != null) + { + await _writeBodyTask; + } + + // Cancel all remaining IO, there might be reads pending if not entire request body was sent by client + AsyncIO?.Dispose(); + + if (_readBodyTask != null) + { + await _readBodyTask; + } + } + return success; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpServer.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpServer.cs new file mode 100644 index 0000000000..bfabf61bd7 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpServer.cs @@ -0,0 +1,286 @@ +// 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.Buffers; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class IISHttpServer : IServer + { + private const string WebSocketVersionString = "WEBSOCKET_VERSION"; + + private static readonly NativeMethods.PFN_REQUEST_HANDLER _requestHandler = HandleRequest; + private static readonly NativeMethods.PFN_SHUTDOWN_HANDLER _shutdownHandler = HandleShutdown; + private static readonly NativeMethods.PFN_DISCONNECT_HANDLER _onDisconnect = OnDisconnect; + private static readonly NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion; + + private IISContextFactory _iisContextFactory; + private readonly MemoryPool _memoryPool = new SlabMemoryPool(); + private GCHandle _httpServerHandle; + private readonly IApplicationLifetime _applicationLifetime; + private readonly ILogger _logger; + private readonly IISServerOptions _options; + private readonly IISNativeApplication _nativeApplication; + + private volatile int _stopping; + private bool Stopping => _stopping == 1; + private int _outstandingRequests; + private readonly TaskCompletionSource _shutdownSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private bool? _websocketAvailable; + + public IFeatureCollection Features { get; } = new FeatureCollection(); + + // TODO: Remove pInProcessHandler argument + public bool IsWebSocketAvailable(IntPtr pInProcessHandler) + { + // Check if the Http upgrade feature is available in IIS. + // To check this, we can look at the server variable WEBSOCKET_VERSION + // And see if there is a version. Same check that Katana did: + // https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Host.SystemWeb/OwinAppContext.cs#L125 + // Actively not locking here as acquiring a lock on every request will hurt perf more than checking the + // server variables a few extra times if a bunch of requests hit the server at the same time. + if (!_websocketAvailable.HasValue) + { + _websocketAvailable = NativeMethods.HttpTryGetServerVariable(pInProcessHandler, WebSocketVersionString, out var webSocketsSupported) + && !string.IsNullOrEmpty(webSocketsSupported); + } + + return _websocketAvailable.Value; + } + + public IISHttpServer( + IISNativeApplication nativeApplication, + IApplicationLifetime applicationLifetime, + IAuthenticationSchemeProvider authentication, + IOptions options, + ILogger logger + ) + { + _nativeApplication = nativeApplication; + _applicationLifetime = applicationLifetime; + _logger = logger; + _options = options.Value; + + if (_options.ForwardWindowsAuthentication) + { + authentication.AddScheme(new AuthenticationScheme(IISServerDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler))); + } + } + + public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) + { + _httpServerHandle = GCHandle.Alloc(this); + + _iisContextFactory = new IISContextFactory(_memoryPool, application, _options, this, _logger); + _nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onDisconnect, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + void RegisterCancelation() + { + cancellationToken.Register(() => + { + _nativeApplication.StopCallsIntoManaged(); + _shutdownSignal.TrySetResult(null); + }); + } + if (Interlocked.Exchange(ref _stopping, 1) == 1) + { + RegisterCancelation(); + + return _shutdownSignal.Task; + } + + // First call back into native saying "DON'T SEND ME ANY MORE REQUESTS" + _nativeApplication.StopIncomingRequests(); + + try + { + // Wait for active requests to drain + if (_outstandingRequests > 0) + { + RegisterCancelation(); + } + else + { + // We have drained all requests. Block any callbacks into managed at this point. + _nativeApplication.StopCallsIntoManaged(); + _shutdownSignal.TrySetResult(null); + } + } + catch (Exception ex) + { + _shutdownSignal.TrySetException(ex); + } + + return _shutdownSignal.Task; + } + + public void Dispose() + { + _stopping = 1; + + // Block any more calls into managed from native as we are unloading. + _nativeApplication.StopCallsIntoManaged(); + _shutdownSignal.TrySetResult(null); + + if (_httpServerHandle.IsAllocated) + { + _httpServerHandle.Free(); + } + + _memoryPool.Dispose(); + _nativeApplication.Dispose(); + } + + private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext) + { + IISHttpServer server = null; + try + { + // Unwrap the server so we can create an http context and process the request + server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target; + Interlocked.Increment(ref server._outstandingRequests); + + var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler); + + ThreadPool.QueueUserWorkItem(state => _ = HandleRequest((IISHttpContext)state), context); + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING; + } + catch (Exception ex) + { + server?._logger.LogError(0, ex, $"Unexpected exception in static {nameof(IISHttpServer)}.{nameof(HandleRequest)}."); + + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST; + } + } + + private static async Task HandleRequest(IISHttpContext context) + { + bool successfulRequest = false; + try + { + successfulRequest = await context.ProcessRequestAsync(); + } + catch (Exception ex) + { + context.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(HandleRequest)}."); + } + finally + { + CompleteRequest(context, successfulRequest); + } + } + + private static bool HandleShutdown(IntPtr pvRequestContext) + { + IISHttpServer server = null; + try + { + server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target; + server._applicationLifetime.StopApplication(); + } + catch (Exception ex) + { + server?._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(HandleShutdown)}."); + } + return true; + } + + + private static void OnDisconnect(IntPtr pvManagedHttpContext) + { + IISHttpContext context = null; + try + { + context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target; + context.ConnectionReset(); + } + catch (Exception ex) + { + context?.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(OnDisconnect)}."); + } + } + + private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes) + { + IISHttpContext context = null; + try + { + context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target; + context.OnAsyncCompletion(hr, bytes); + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING; + } + catch (Exception ex) + { + context?.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(OnAsyncCompletion)}."); + + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST; + } + } + + private static void CompleteRequest(IISHttpContext context, bool result) + { + // Post completion after completing the request to resume the state machine + context.PostCompletion(ConvertRequestCompletionResults(result)); + + if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping) + { + // All requests have been drained. + context.Server._nativeApplication.StopCallsIntoManaged(); + context.Server._shutdownSignal.TrySetResult(null); + } + + // Dispose the context + context.Dispose(); + } + + private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success) + { + return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE + : NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST; + } + + private class IISContextFactory : IISContextFactory + { + private readonly IHttpApplication _application; + private readonly MemoryPool _memoryPool; + private readonly IISServerOptions _options; + private readonly IISHttpServer _server; + private readonly ILogger _logger; + + public IISContextFactory(MemoryPool memoryPool, IHttpApplication application, IISServerOptions options, IISHttpServer server, ILogger logger) + { + _application = application; + _memoryPool = memoryPool; + _options = options; + _server = server; + _logger = logger; + } + + public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler) + { + return new IISHttpContextOfT(_memoryPool, _application, pInProcessHandler, _options, _server, _logger); + } + } + } + + // Over engineering to avoid allocations... + internal interface IISContextFactory + { + IISHttpContext CreateHttpContext(IntPtr pInProcessHandler); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs new file mode 100644 index 0000000000..1be8e888fd --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISNativeApplication.cs @@ -0,0 +1,57 @@ +// 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; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class IISNativeApplication + { + private readonly IntPtr _nativeApplication; + + public IISNativeApplication(IntPtr nativeApplication) + { + _nativeApplication = nativeApplication; + } + + public void StopIncomingRequests() + { + NativeMethods.HttpStopIncomingRequests(_nativeApplication); + } + + public void StopCallsIntoManaged() + { + NativeMethods.HttpStopCallsIntoManaged(_nativeApplication); + } + + public void RegisterCallbacks( + NativeMethods.PFN_REQUEST_HANDLER requestHandler, + NativeMethods.PFN_SHUTDOWN_HANDLER shutdownHandler, + NativeMethods.PFN_DISCONNECT_HANDLER disconnectHandler, + NativeMethods.PFN_ASYNC_COMPLETION onAsyncCompletion, + IntPtr requestContext, + IntPtr shutdownContext) + { + NativeMethods.HttpRegisterCallbacks( + _nativeApplication, + requestHandler, + shutdownHandler, + disconnectHandler, + onAsyncCompletion, + requestContext, + shutdownContext); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + ~IISNativeApplication() + { + // If this finalize is invoked, try our best to block all calls into managed. + StopCallsIntoManaged(); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerAuthenticationHandler.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerAuthenticationHandler.cs new file mode 100644 index 0000000000..7031df34ea --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerAuthenticationHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + public class IISServerAuthenticationHandler : IAuthenticationHandler + { + private HttpContext _context; + private IISHttpContext _iisHttpContext; + + internal AuthenticationScheme Scheme { get; private set; } + + public Task AuthenticateAsync() + { + var user = _iisHttpContext.WindowsUser; + if (user != null && user.Identity != null && user.Identity.IsAuthenticated) + { + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name))); + } + else + { + return Task.FromResult(AuthenticateResult.NoResult()); + } + } + + public Task ChallengeAsync(AuthenticationProperties properties) + { + // We would normally set the www-authenticate header here, but IIS does that for us. + _context.Response.StatusCode = 401; + return Task.CompletedTask; + } + + public Task ForbidAsync(AuthenticationProperties properties) + { + _context.Response.StatusCode = 403; + return Task.CompletedTask; + } + + public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) + { + _iisHttpContext = context.Features.Get(); + if (_iisHttpContext == null) + { + throw new InvalidOperationException("No IISHttpContext found."); + } + + Scheme = scheme; + _context = context; + + return Task.CompletedTask; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerSetupFilter.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerSetupFilter.cs new file mode 100644 index 0000000000..f3c3613565 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IISServerSetupFilter.cs @@ -0,0 +1,36 @@ +// 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.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class IISServerSetupFilter : IStartupFilter + { + private string _virtualPath; + + public IISServerSetupFilter(string virtualPath) + { + _virtualPath = virtualPath; + } + + public Action Configure(Action next) + { + return app => + { + var server = app.ApplicationServices.GetService(); + if (server?.GetType() != typeof(IISHttpServer)) + { + throw new InvalidOperationException("Application is running inside IIS process but is not configured to use IIS server."); + } + + app.UsePathBase(_virtualPath); + next(app); + }; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Flush.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Flush.cs new file mode 100644 index 0000000000..1187c5b3f2 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Flush.cs @@ -0,0 +1,43 @@ +// 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; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class AsyncIOEngine + { + internal class AsyncFlushOperation : AsyncIOOperation + { + private readonly AsyncIOEngine _engine; + + private IntPtr _requestHandler; + + public AsyncFlushOperation(AsyncIOEngine engine) + { + _engine = engine; + } + + public void Initialize(IntPtr requestHandler) + { + _requestHandler = requestHandler; + } + + protected override bool InvokeOperation(out int hr, out int bytes) + { + bytes = 0; + hr = NativeMethods.HttpFlushResponseBytes(_requestHandler, out var fCompletionExpected); + + return !fCompletionExpected; + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _requestHandler = default; + _engine.ReturnOperation(this); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Read.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Read.cs new file mode 100644 index 0000000000..00c6709453 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Read.cs @@ -0,0 +1,63 @@ +// 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.Buffers; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class AsyncIOEngine + { + internal class AsyncReadOperation : AsyncIOOperation + { + private readonly AsyncIOEngine _engine; + + private MemoryHandle _inputHandle; + + private IntPtr _requestHandler; + + private Memory _memory; + + public AsyncReadOperation(AsyncIOEngine engine) + { + _engine = engine; + } + + public void Initialize(IntPtr requestHandler, Memory memory) + { + _requestHandler = requestHandler; + _memory = memory; + } + + protected override unsafe bool InvokeOperation(out int hr, out int bytes) + { + _inputHandle = _memory.Pin(); + hr = NativeMethods.HttpReadRequestBytes( + _requestHandler, + (byte*)_inputHandle.Pointer, + _memory.Length, + out bytes, + out bool completionExpected); + + return !completionExpected; + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _memory = default; + _inputHandle.Dispose(); + _inputHandle = default; + _requestHandler = default; + + _engine.ReturnOperation(this); + } + + public override void FreeOperationResources(int hr, int bytes) + { + _inputHandle.Dispose(); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Write.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Write.cs new file mode 100644 index 0000000000..72be9f107b --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.Write.cs @@ -0,0 +1,34 @@ +// 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.HttpSys.Internal; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class AsyncIOEngine + { + private class AsyncWriteOperation : AsyncWriteOperationBase + { + private readonly AsyncIOEngine _engine; + + public AsyncWriteOperation(AsyncIOEngine engine) + { + _engine = engine; + } + + protected override unsafe int WriteChunks(IntPtr requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks, + out bool completionExpected) + { + return NativeMethods.HttpWriteResponseBytes(requestHandler, dataChunks, chunkCount, out completionExpected); + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _engine.ReturnOperation(this); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.cs new file mode 100644 index 0000000000..f169cebf77 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOEngine.cs @@ -0,0 +1,176 @@ +// 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.Buffers; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class AsyncIOEngine : IAsyncIOEngine + { + private readonly object _contextSync; + private readonly IntPtr _handler; + + private bool _stopped; + + private AsyncIOOperation _nextOperation; + private AsyncIOOperation _runningOperation; + + private AsyncReadOperation _cachedAsyncReadOperation; + private AsyncWriteOperation _cachedAsyncWriteOperation; + private AsyncFlushOperation _cachedAsyncFlushOperation; + + public AsyncIOEngine(object contextSync, IntPtr handler) + { + _contextSync = contextSync; + _handler = handler; + } + + public ValueTask ReadAsync(Memory memory) + { + var read = GetReadOperation(); + read.Initialize(_handler, memory); + Run(read); + return new ValueTask(read, 0); + } + + public ValueTask WriteAsync(ReadOnlySequence data) + { + var write = GetWriteOperation(); + write.Initialize(_handler, data); + Run(write); + return new ValueTask(write, 0); + } + + private void Run(AsyncIOOperation ioOperation) + { + lock (_contextSync) + { + if (_stopped) + { + // Abort all operation after IO was stopped + ioOperation.Complete(NativeMethods.ERROR_OPERATION_ABORTED, 0); + return; + } + + if (_runningOperation != null) + { + if (_nextOperation == null) + { + _nextOperation = ioOperation; + + // If there is an active read cancel it + if (_runningOperation is AsyncReadOperation) + { + NativeMethods.HttpTryCancelIO(_handler); + } + } + else + { + throw new InvalidOperationException("Only one queued operation is allowed"); + } + } + else + { + // we are just starting operation so there would be no + // continuation registered + var completed = ioOperation.Invoke() != null; + + // operation went async + if (!completed) + { + _runningOperation = ioOperation; + } + } + } + } + + + public ValueTask FlushAsync() + { + var flush = GetFlushOperation(); + flush.Initialize(_handler); + Run(flush); + return new ValueTask(flush, 0); + } + + public void NotifyCompletion(int hr, int bytes) + { + AsyncIOOperation.AsyncContinuation continuation; + AsyncIOOperation.AsyncContinuation? nextContinuation = null; + + lock (_contextSync) + { + Debug.Assert(_runningOperation != null); + + continuation = _runningOperation.Complete(hr, bytes); + + var next = _nextOperation; + _nextOperation = null; + _runningOperation = null; + + if (next != null) + { + if (_stopped) + { + // Abort next operation if IO is stopped + nextContinuation = next.Complete(NativeMethods.ERROR_OPERATION_ABORTED, 0); + } + else + { + nextContinuation = next.Invoke(); + + // operation went async + if (nextContinuation == null) + { + _runningOperation = next; + } + } + } + } + + continuation.Invoke(); + nextContinuation?.Invoke(); + } + + public void Dispose() + { + lock (_contextSync) + { + _stopped = true; + NativeMethods.HttpTryCancelIO(_handler); + } + } + + private AsyncReadOperation GetReadOperation() => + Interlocked.Exchange(ref _cachedAsyncReadOperation, null) ?? + new AsyncReadOperation(this); + + private AsyncWriteOperation GetWriteOperation() => + Interlocked.Exchange(ref _cachedAsyncWriteOperation, null) ?? + new AsyncWriteOperation(this); + + private AsyncFlushOperation GetFlushOperation() => + Interlocked.Exchange(ref _cachedAsyncFlushOperation, null) ?? + new AsyncFlushOperation(this); + + private void ReturnOperation(AsyncReadOperation operation) + { + Volatile.Write(ref _cachedAsyncReadOperation, operation); + } + + private void ReturnOperation(AsyncWriteOperation operation) + { + Volatile.Write(ref _cachedAsyncWriteOperation, operation); + } + + private void ReturnOperation(AsyncFlushOperation operation) + { + Volatile.Write(ref _cachedAsyncFlushOperation, operation); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs new file mode 100644 index 0000000000..80e9234ea2 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs @@ -0,0 +1,162 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks.Sources; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal abstract class AsyncIOOperation: IValueTaskSource, IValueTaskSource + { + private static readonly Action CallbackCompleted = _ => { Debug.Assert(false, "Should not be invoked"); }; + + private Action _continuation; + private object _state; + private int _result; + + private Exception _exception; + + public ValueTaskSourceStatus GetStatus(short token) + { + if (ReferenceEquals(Volatile.Read(ref _continuation), null)) + { + return ValueTaskSourceStatus.Pending; + } + + return _exception != null ? ValueTaskSourceStatus.Succeeded : ValueTaskSourceStatus.Faulted; + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + if (_state != null) + { + ThrowMultipleContinuations(); + } + + _state = state; + + var previousContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null); + + if (previousContinuation != null) + { + if (!ReferenceEquals(previousContinuation, CallbackCompleted)) + { + ThrowMultipleContinuations(); + } + + new AsyncContinuation(continuation, state).Invoke(); + } + } + + private static void ThrowMultipleContinuations() + { + throw new InvalidOperationException("Multiple awaiters are not allowed"); + } + + void IValueTaskSource.GetResult(short token) + { + var exception = _exception; + + ResetOperation(); + + if (exception != null) + { + throw exception; + } + } + + public int GetResult(short token) + { + var exception = _exception; + var result = _result; + + ResetOperation(); + + if (exception != null) + { + throw exception; + } + + return result; + } + + public AsyncContinuation? Invoke() + { + if (InvokeOperation(out var hr, out var bytes)) + { + return Complete(hr, bytes); + } + return null; + } + + protected abstract bool InvokeOperation(out int hr, out int bytes); + + public AsyncContinuation Complete(int hr, int bytes) + { + if (hr != NativeMethods.ERROR_OPERATION_ABORTED) + { + _result = bytes; + if (hr != NativeMethods.HR_OK) + { + // Treat all errors as the client disconnect + _exception = new ConnectionResetException("The client has disconnected", Marshal.GetExceptionForHR(hr)); + } + } + else + { + _result = -1; + _exception = null; + } + + AsyncContinuation asyncContinuation = default; + var continuation = Interlocked.CompareExchange(ref _continuation, CallbackCompleted, null); + if (continuation != null) + { + asyncContinuation = new AsyncContinuation(continuation, _state); + } + + FreeOperationResources(hr, bytes); + + return asyncContinuation; + } + + public virtual void FreeOperationResources(int hr, int bytes) { } + + protected virtual void ResetOperation() + { + _exception = null; + _result = int.MinValue; + _state = null; + _continuation = null; + } + + public readonly struct AsyncContinuation + { + public Action Continuation { get; } + public object State { get; } + + public AsyncContinuation(Action continuation, object state) + { + Continuation = continuation; + State = state; + } + + public void Invoke() + { + if (Continuation != null) + { + // TODO: use generic overload when code moved to be netcoreapp only + var continuation = Continuation; + var state = State; + ThreadPool.QueueUserWorkItem(_ => continuation(state)); + } + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncWriteOperationBase.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncWriteOperationBase.cs new file mode 100644 index 0000000000..3be67095fd --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncWriteOperationBase.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; +using System.Buffers; +using Microsoft.AspNetCore.HttpSys.Internal; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal abstract class AsyncWriteOperationBase : AsyncIOOperation + { + private const int HttpDataChunkStackLimit = 128; // 16 bytes per HTTP_DATA_CHUNK + + private IntPtr _requestHandler; + private ReadOnlySequence _buffer; + private MemoryHandle[] _handles; + + public void Initialize(IntPtr requestHandler, ReadOnlySequence buffer) + { + _requestHandler = requestHandler; + _buffer = buffer; + } + + protected override unsafe bool InvokeOperation(out int hr, out int bytes) + { + if (_buffer.Length > int.MaxValue) + { + throw new InvalidOperationException($"Writes larger then {int.MaxValue} are not supported."); + } + + bool completionExpected; + var chunkCount = GetChunkCount(); + + var bufferLength = (int)_buffer.Length; + + if (chunkCount < HttpDataChunkStackLimit) + { + // To avoid stackoverflows, we will only stackalloc if the write size is less than the StackChunkLimit + // The stack size is IIS is by default 128/256 KB, so we are generous with this threshold. + var chunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[chunkCount]; + hr = WriteSequence(chunkCount, _buffer, chunks, out completionExpected); + } + else + { + // Otherwise allocate the chunks on the heap. + var chunks = new HttpApiTypes.HTTP_DATA_CHUNK[chunkCount]; + fixed (HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks = chunks) + { + hr = WriteSequence(chunkCount, _buffer, pDataChunks, out completionExpected); + } + } + + bytes = bufferLength; + return !completionExpected; + } + + public override void FreeOperationResources(int hr, int bytes) + { + // Free the handles + foreach (var handle in _handles) + { + handle.Dispose(); + } + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _requestHandler = default; + _buffer = default; + _handles.AsSpan().Clear(); + } + + private int GetChunkCount() + { + if (_buffer.IsSingleSegment) + { + return 1; + } + + var count = 0; + + foreach (var _ in _buffer) + { + count++; + } + + return count; + } + + private unsafe int WriteSequence(int nChunks, ReadOnlySequence buffer, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, out bool fCompletionExpected) + { + var currentChunk = 0; + + if (_handles == null || _handles.Length < nChunks) + { + _handles = new MemoryHandle[nChunks]; + } + + foreach (var readOnlyMemory in buffer) + { + ref var handle = ref _handles[currentChunk]; + ref var chunk = ref pDataChunks[currentChunk]; + handle = readOnlyMemory.Pin(); + + chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; + chunk.fromMemory.BufferLength = (uint)readOnlyMemory.Length; + chunk.fromMemory.pBuffer = (IntPtr)handle.Pointer; + + currentChunk++; + } + + return WriteChunks(_requestHandler, nChunks, pDataChunks, out fCompletionExpected); + } + + protected abstract unsafe int WriteChunks(IntPtr requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks, out bool completionExpected); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/IAsyncIOEngine.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/IAsyncIOEngine.cs new file mode 100644 index 0000000000..6e6896b8c4 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/IAsyncIOEngine.cs @@ -0,0 +1,17 @@ +// 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.Buffers; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal interface IAsyncIOEngine: IDisposable + { + ValueTask ReadAsync(Memory memory); + ValueTask WriteAsync(ReadOnlySequence data); + ValueTask FlushAsync(); + void NotifyCompletion(int hr, int bytes); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Initialize.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Initialize.cs new file mode 100644 index 0000000000..10e1f0da7d --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Initialize.cs @@ -0,0 +1,42 @@ +// 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; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class WebSocketsAsyncIOEngine + { + internal class AsyncInitializeOperation : AsyncIOOperation + { + private readonly WebSocketsAsyncIOEngine _engine; + + private IntPtr _requestHandler; + + public AsyncInitializeOperation(WebSocketsAsyncIOEngine engine) + { + _engine = engine; + } + + public void Initialize(IntPtr requestHandler) + { + _requestHandler = requestHandler; + } + + protected override bool InvokeOperation(out int hr, out int bytes) + { + hr = NativeMethods.HttpFlushResponseBytes(_requestHandler, out var completionExpected); + bytes = 0; + return !completionExpected; + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _requestHandler = default; + _engine.ReturnOperation(this); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Read.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Read.cs new file mode 100644 index 0000000000..413fa77703 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Read.cs @@ -0,0 +1,79 @@ +// 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.Buffers; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class WebSocketsAsyncIOEngine + { + internal class WebSocketReadOperation : AsyncIOOperation + { + public static readonly NativeMethods.PFN_WEBSOCKET_ASYNC_COMPLETION ReadCallback = (httpContext, completionInfo, completionContext) => + { + var context = (WebSocketReadOperation)GCHandle.FromIntPtr(completionContext).Target; + + NativeMethods.HttpGetCompletionInfo(completionInfo, out var cbBytes, out var hr); + + var continuation = context.Complete(hr, cbBytes); + + continuation.Invoke(); + + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING; + }; + + private readonly WebSocketsAsyncIOEngine _engine; + private readonly GCHandle _thisHandle; + private MemoryHandle _inputHandle; + private IntPtr _requestHandler; + private Memory _memory; + + public WebSocketReadOperation(WebSocketsAsyncIOEngine engine) + { + _engine = engine; + _thisHandle = GCHandle.Alloc(this); + } + + protected override unsafe bool InvokeOperation(out int hr, out int bytes) + { + _inputHandle = _memory.Pin(); + + hr = NativeMethods.HttpWebsocketsReadBytes( + _requestHandler, + (byte*)_inputHandle.Pointer, + _memory.Length, + ReadCallback, + (IntPtr)_thisHandle, + out bytes, + out var completionExpected); + + return !completionExpected; + } + + public void Initialize(IntPtr requestHandler, Memory memory) + { + _requestHandler = requestHandler; + _memory = memory; + } + + public override void FreeOperationResources(int hr, int bytes) + { + _inputHandle.Dispose(); + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _memory = default; + _inputHandle.Dispose(); + _inputHandle = default; + _requestHandler = default; + + _engine.ReturnOperation(this); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Write.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Write.cs new file mode 100644 index 0000000000..3eff3bba46 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.Write.cs @@ -0,0 +1,49 @@ +// 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.Runtime.InteropServices; +using Microsoft.AspNetCore.HttpSys.Internal; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class WebSocketsAsyncIOEngine + { + internal sealed class WebSocketWriteOperation : AsyncWriteOperationBase + { + + private static readonly NativeMethods.PFN_WEBSOCKET_ASYNC_COMPLETION WriteCallback = (httpContext, completionInfo, completionContext) => + { + var context = (WebSocketWriteOperation)GCHandle.FromIntPtr(completionContext).Target; + + NativeMethods.HttpGetCompletionInfo(completionInfo, out var cbBytes, out var hr); + + var continuation = context.Complete(hr, cbBytes); + continuation.Invoke(); + + return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING; + }; + + private readonly WebSocketsAsyncIOEngine _engine; + private readonly GCHandle _thisHandle; + + public WebSocketWriteOperation(WebSocketsAsyncIOEngine engine) + { + _engine = engine; + _thisHandle = GCHandle.Alloc(this); + } + + protected override unsafe int WriteChunks(IntPtr requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks, out bool completionExpected) + { + return NativeMethods.HttpWebsocketsWriteBytes(requestHandler, dataChunks, chunkCount, WriteCallback, (IntPtr)_thisHandle, out completionExpected); + } + + protected override void ResetOperation() + { + base.ResetOperation(); + + _engine.ReturnOperation(this); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.cs new file mode 100644 index 0000000000..38c737dbce --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/IO/WebSocketsAsyncIOEngine.cs @@ -0,0 +1,142 @@ +// 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.Buffers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core.IO +{ + internal partial class WebSocketsAsyncIOEngine: IAsyncIOEngine + { + private readonly object _contextLock; + + private readonly IntPtr _handler; + + private bool _isInitialized; + + private AsyncInitializeOperation _initializationFlush; + + private WebSocketWriteOperation _cachedWebSocketWriteOperation; + + private WebSocketReadOperation _cachedWebSocketReadOperation; + + private AsyncInitializeOperation _cachedAsyncInitializeOperation; + + public WebSocketsAsyncIOEngine(object contextLock, IntPtr handler) + { + _contextLock = contextLock; + _handler = handler; + } + + public ValueTask ReadAsync(Memory memory) + { + lock (_contextLock) + { + ThrowIfNotInitialized(); + + var read = GetReadOperation(); + read.Initialize(_handler, memory); + read.Invoke(); + return new ValueTask(read, 0); + } + } + + public ValueTask WriteAsync(ReadOnlySequence data) + { + lock (_contextLock) + { + ThrowIfNotInitialized(); + + var write = GetWriteOperation(); + write.Initialize(_handler, data); + write.Invoke(); + return new ValueTask(write, 0); + } + } + + public ValueTask FlushAsync() + { + lock (_contextLock) + { + if (_isInitialized) + { + return new ValueTask(Task.CompletedTask); + } + + NativeMethods.HttpEnableWebsockets(_handler); + + var init = GetInitializeOperation(); + init.Initialize(_handler); + + var continuation = init.Invoke(); + + if (continuation != null) + { + _isInitialized = true; + } + else + { + _initializationFlush = init; + } + + return new ValueTask(init, 0); + } + } + + public void NotifyCompletion(int hr, int bytes) + { + _isInitialized = true; + + var init = _initializationFlush; + if (init == null) + { + throw new InvalidOperationException("Unexpected completion for WebSocket operation"); + } + + var continuation = init.Complete(hr, bytes); + + _initializationFlush = null; + + continuation.Invoke(); + } + + private void ThrowIfNotInitialized() + { + if (!_isInitialized) + { + throw new InvalidOperationException("Socket IO not initialized yet"); + } + } + + public void Dispose() + { + lock (_contextLock) + { + NativeMethods.HttpTryCancelIO(_handler); + } + } + + private WebSocketReadOperation GetReadOperation() => + Interlocked.Exchange(ref _cachedWebSocketReadOperation, null) ?? + new WebSocketReadOperation(this); + + private WebSocketWriteOperation GetWriteOperation() => + Interlocked.Exchange(ref _cachedWebSocketWriteOperation, null) ?? + new WebSocketWriteOperation(this); + + private AsyncInitializeOperation GetInitializeOperation() => + Interlocked.Exchange(ref _cachedAsyncInitializeOperation, null) ?? + new AsyncInitializeOperation(this); + + private void ReturnOperation(AsyncInitializeOperation operation) => + Volatile.Write(ref _cachedAsyncInitializeOperation, operation); + + private void ReturnOperation(WebSocketWriteOperation operation) => + Volatile.Write(ref _cachedWebSocketWriteOperation, operation); + + private void ReturnOperation(WebSocketReadOperation operation) => + Volatile.Write(ref _cachedWebSocketReadOperation, operation); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs new file mode 100644 index 0000000000..431f5c809a --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/OutputProducer.cs @@ -0,0 +1,152 @@ +// 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.Buffers; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class OutputProducer + { + // This locks access to to all of the below fields + private readonly object _contextLock = new object(); + + private ValueTask _flushTask; + private bool _completed = false; + + private readonly Pipe _pipe; + + // https://github.com/dotnet/corefxlab/issues/1334 + // Pipelines don't support multiple awaiters on flush + // this is temporary until it does + private TaskCompletionSource _flushTcs; + private readonly object _flushLock = new object(); + private Action _flushCompleted; + + public OutputProducer(Pipe pipe) + { + _pipe = pipe; + _flushCompleted = OnFlushCompleted; + } + + public PipeReader Reader => _pipe.Reader; + + public Task FlushAsync(CancellationToken cancellationToken) + { + _pipe.Reader.CancelPendingRead(); + // Await backpressure + return FlushAsync(_pipe.Writer, cancellationToken); + } + + public void Dispose() + { + lock (_contextLock) + { + if (_completed) + { + return; + } + + _completed = true; + _pipe.Writer.Complete(); + } + } + + public void Abort(Exception error) + { + lock (_contextLock) + { + if (_completed) + { + return; + } + + _completed = true; + + _pipe.Reader.CancelPendingRead(); + _pipe.Writer.Complete(); + } + } + + public Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + lock (_contextLock) + { + if (_completed) + { + return Task.CompletedTask; + } + + _pipe.Writer.Write(buffer.Span); + } + + return FlushAsync(_pipe.Writer, cancellationToken); + } + + private Task FlushAsync(PipeWriter pipeWriter, CancellationToken cancellationToken) + { + var awaitable = pipeWriter.FlushAsync(cancellationToken); + if (awaitable.IsCompleted) + { + // The flush task can't fail today + return Task.CompletedTask; + } + return FlushAsyncAwaited(awaitable, cancellationToken); + } + + private async Task FlushAsyncAwaited(ValueTask awaitable, CancellationToken cancellationToken) + { + // https://github.com/dotnet/corefxlab/issues/1334 + // Since the flush awaitable doesn't currently support multiple awaiters + // we need to use a task to track the callbacks. + // All awaiters get the same task + lock (_flushLock) + { + _flushTask = awaitable; + if (_flushTcs == null || _flushTcs.Task.IsCompleted) + { + _flushTcs = new TaskCompletionSource(); + + _flushTask.GetAwaiter().OnCompleted(_flushCompleted); + } + } + + try + { + await _flushTcs.Task; + cancellationToken.ThrowIfCancellationRequested(); + } + catch (OperationCanceledException ex) + { + Abort(new ConnectionAbortedException(CoreStrings.ConnectionOrStreamAbortedByCancellationToken, ex)); + } + catch + { + // A canceled token is the only reason flush should ever throw. + Debug.Assert(false); + } + } + + private void OnFlushCompleted() + { + try + { + _flushTask.GetAwaiter().GetResult(); + _flushTcs.TrySetResult(null); + } + catch (Exception exception) + { + _flushTcs.TrySetResult(exception); + } + finally + { + _flushTask = default; + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ReadOnlyStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ReadOnlyStream.cs new file mode 100644 index 0000000000..b5bbbf6199 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ReadOnlyStream.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal abstract class ReadOnlyStream : Stream + { + public override bool CanRead => true; + + public override bool CanWrite => false; + + public override int WriteTimeout + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override bool CanSeek => false; + + public override long Length + => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/Streams.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/Streams.cs new file mode 100644 index 0000000000..bc50edd01b --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/Streams.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 System; +using System.IO; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class Streams + { + private static readonly ThrowingWasUpgradedWriteOnlyStream _throwingResponseStream + = new ThrowingWasUpgradedWriteOnlyStream(); + + private readonly IISHttpContext _context; + private readonly HttpResponseStream _response; + private readonly HttpRequestStream _request; + private readonly WrappingStream _upgradeableRequest; + private readonly WrappingStream _upgradeableResponse; + private EmptyStream _emptyRequest; + private Stream _upgradeStream; + + public Streams(IISHttpContext context) + { + _context = context; + _request = new HttpRequestStream(_context); + _response = new HttpResponseStream(_context, _context); + _upgradeableResponse = new WrappingStream(_response); + _upgradeableRequest = new WrappingStream(_request); + } + + public Stream Upgrade() + { + _upgradeStream = new HttpUpgradeStream(_request, _response); + + // causes writes to context.Response.Body to throw + _upgradeableResponse.SetInnerStream(_throwingResponseStream); + + _emptyRequest = new EmptyStream(_context); + + _upgradeableRequest.SetInnerStream(_emptyRequest); + // _upgradeStream always uses _response + return _upgradeStream; + } + + public (Stream request, Stream response) Start() + { + _request.StartAcceptingReads(_context); + _response.StartAcceptingWrites(); + + return (_upgradeableRequest, _upgradeableResponse); + } + + public void Stop() + { + _request.StopAcceptingReads(); + _emptyRequest?.StopAcceptingReads(); + _response.StopAcceptingWrites(); + } + + public void Abort(Exception error) + { + _request.Abort(error); + _emptyRequest?.Abort(error); + _response.Abort(); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ThrowingWasUpgradedWriteOnlyStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ThrowingWasUpgradedWriteOnlyStream.cs new file mode 100644 index 0000000000..3990cd9865 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/ThrowingWasUpgradedWriteOnlyStream.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + public class ThrowingWasUpgradedWriteOnlyStream : WriteOnlyStream + { + public override void Write(byte[] buffer, int offset, int count) + => throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded); + + public override void Flush() + => throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded); + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WrappingStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WrappingStream.cs new file mode 100644 index 0000000000..18ae443711 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WrappingStream.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + internal class WrappingStream : Stream + { + private Stream _inner; + private bool _disposed; + + public WrappingStream(Stream inner) + { + _inner = inner; + } + + public void SetInnerStream(Stream inner) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(WrappingStream)); + } + + _inner = inner; + } + + public override bool CanRead => _inner.CanRead; + + public override bool CanSeek => _inner.CanSeek; + + public override bool CanWrite => _inner.CanWrite; + + public override bool CanTimeout => _inner.CanTimeout; + + public override long Length => _inner.Length; + + public override long Position + { + get => _inner.Position; + set => _inner.Position = value; + } + + public override int ReadTimeout + { + get => _inner.ReadTimeout; + set => _inner.ReadTimeout = value; + } + + public override int WriteTimeout + { + get => _inner.WriteTimeout; + set => _inner.WriteTimeout = value; + } + + public override void Flush() + => _inner.Flush(); + + public override Task FlushAsync(CancellationToken cancellationToken) + => _inner.FlushAsync(cancellationToken); + + public override int Read(byte[] buffer, int offset, int count) + => _inner.Read(buffer, offset, count); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => _inner.ReadAsync(buffer, offset, count, cancellationToken); + +#if NETCOREAPP2_1 + public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) + => _inner.ReadAsync(destination, cancellationToken); +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + public override int ReadByte() + => _inner.ReadByte(); + + public override long Seek(long offset, SeekOrigin origin) + => _inner.Seek(offset, origin); + + public override void SetLength(long value) + => _inner.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + => _inner.Write(buffer, offset, count); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => _inner.WriteAsync(buffer, offset, count, cancellationToken); + +#if NETCOREAPP2_1 + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) + => _inner.WriteAsync(source, cancellationToken); +#elif NETSTANDARD2_0 +#else +#error TFMs need to be updated +#endif + + public override void WriteByte(byte value) + => _inner.WriteByte(value); + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + => _inner.CopyToAsync(destination, bufferSize, cancellationToken); + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + => _inner.BeginRead(buffer, offset, count, callback, state); + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + => _inner.BeginWrite(buffer, offset, count, callback, state); + + public override int EndRead(IAsyncResult asyncResult) + => _inner.EndRead(asyncResult); + + public override void EndWrite(IAsyncResult asyncResult) + => _inner.EndWrite(asyncResult); + + public override object InitializeLifetimeService() + => _inner.InitializeLifetimeService(); + + public override void Close() + => _inner.Close(); + + public override bool Equals(object obj) + => _inner.Equals(obj); + + public override int GetHashCode() + => _inner.GetHashCode(); + + public override string ToString() + => _inner.ToString(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _disposed = true; + _inner.Dispose(); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WriteOnlyStream.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WriteOnlyStream.cs new file mode 100644 index 0000000000..9ccf9c9cd1 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Core/WriteOnlyStream.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.IIS.Core +{ + public abstract class WriteOnlyStream : Stream + { + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override int ReadTimeout + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/CoreStrings.resx b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/CoreStrings.resx new file mode 100644 index 0000000000..d6c2e7b974 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/CoreStrings.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot write to response body after connection has been upgraded. + + + The response has been aborted due to an unhandled application exception. + + + Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + + + IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection. + + + Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + + + Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. + + + Cannot write to the response body, the response has completed. + + + The connection was aborted by the application. + + + The connection or stream was aborted because a write operation was aborted with a CancellationToken. + + + {name} cannot be set because the response has already started. + + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/HttpContextExtensions.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/HttpContextExtensions.cs new file mode 100644 index 0000000000..9c0092d3fe --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/HttpContextExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.IIS +{ + /// + /// Extensions to that enable access to IIS features. + /// + public static class HttpContextExtensions + { + /// + /// Gets the value of a server variable for the current request. + /// + /// The http context for the request. + /// The name of the variable. + /// + /// null if the feature does not support the feature. + /// May return null or empty if the variable does not exist or is not set. + /// + /// + /// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471. + /// + public static string GetIISServerVariable(this HttpContext context, string variableName) + { + var feature = context.Features.Get(); + + if (feature == null) + { + return null; + } + + return feature[variableName]; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerDefaults.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerDefaults.cs new file mode 100644 index 0000000000..5ac1296597 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerDefaults.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Server.IIS +{ + public class IISServerDefaults + { + public const string AuthenticationScheme = "Windows"; + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerOptions.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerOptions.cs new file mode 100644 index 0000000000..feed9180a5 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IISServerOptions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Builder +{ + public class IISServerOptions + { + /// + /// If true the server should set HttpContext.User. If false the server will only provide an + /// identity when explicitly requested by the AuthenticationScheme. + /// Note Windows Authentication must also be enabled in IIS for this to work. + /// + public bool AutomaticAuthentication { get; set; } = true; + + /// + /// Sets the display name shown to users on login pages. The default is null. + /// + public string AuthenticationDisplayName { get; set; } + + /// + /// Used to indicate if the authentication handler should be registered. This is only done if ANCM indicates + /// IIS has a non-anonymous authentication enabled, or for back compat with ANCMs that did not provide this information. + /// + internal bool ForwardWindowsAuthentication { get; set; } = true; + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IServerVariableFeature.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IServerVariableFeature.cs new file mode 100644 index 0000000000..44f667cb41 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/IServerVariableFeature.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Http.Features +{ + /// + /// This feature provides access to request server variables set. + /// + /// This feature is only available when hosting ASP.NET Core in-process with IIS or IIS Express. + /// + /// + /// + /// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471. + /// + public interface IServerVariablesFeature + { + /// + /// Gets or sets the value of a server variable for the current request. + /// + /// The variable name + /// May return null or empty if the variable does not exist or is not set. + string this[string variableName] { get; set; } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj index 51cc16d958..53145c2b29 100644 --- a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.csproj @@ -1,56 +1,74 @@ - - - + - netcoreapp2.1 + netstandard2.0 Microsoft.AspNetCore.Server.IIS Provides support for hosting ASP.NET Core in IIS using the AspNetCoreModule. - false - false - false - false - false - true - $(PackageId).nuspec - true + $(NoWarn);CS1591 + true + aspnetcore;iis + true + true + netcoreapp2.2 + True + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + False + - + - - - - id=$(PackageId); - tfm=$(TargetFramework); - tfmGroup=$(TargetFrameworkIdentifier)$(_TargetFrameworkVersionWithoutV); - configuration=$(Configuration); - copyright=$(Copyright); - author=$(Authors); - licenseUrl=$(PackageLicenseUrl); - iconUrl=$(PackageIconUrl); - projectUrl=$(PackageProjectUrl); - repositoryUrl=$(RepositoryUrl); - repositoryType=$(RepositoryType); - repositoryCommit=$(RepositoryCommit); - version=$(PackageVersion); - description=$(Description); - serviceable=$([MSBuild]::ValueOrDefault('$(Serviceable)', 'false')); - - + + + + + + - - - - - - + + + + + + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec deleted file mode 100644 index 3124aad25f..0000000000 --- a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.nuspec +++ /dev/null @@ -1,32 +0,0 @@ - - - - Microsoft.AspNetCore.Server.IIS - $version$ - $author$ - $licenseUrl$ - $copyright$ - $projectUrl$ - $iconUrl$ - true - $description$ - en-US - aspnetcore iis - $serviceable$ - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.targets b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.targets index fe0dbb05d5..a25bbf3b4f 100644 --- a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.targets +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Microsoft.AspNetCore.Server.IIS.targets @@ -3,7 +3,11 @@ Capability that enables Visual Studio support for hosting Asp.Net Core applications in the IIS process --> - + + + AspNetCoreModuleV2 + + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs new file mode 100644 index 0000000000..4d24b3dd77 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/NativeMethods.cs @@ -0,0 +1,305 @@ +// 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.Runtime.InteropServices; +using Microsoft.AspNetCore.HttpSys.Internal; +using Microsoft.AspNetCore.Server.IIS.Core; + +namespace Microsoft.AspNetCore.Server.IIS +{ + internal static class NativeMethods + { + internal const int HR_OK = 0; + internal const int ERROR_NOT_FOUND = unchecked((int)0x80070490); + internal const int ERROR_OPERATION_ABORTED = unchecked((int)0x800703E3); + internal const int ERROR_INVALID_PARAMETER = unchecked((int)0x80070057); + internal const int COR_E_IO = unchecked((int)0x80131620); + + private const string KERNEL32 = "kernel32.dll"; + + internal const string AspNetCoreModuleDll = "aspnetcorev2_inprocess.dll"; + + [DllImport(KERNEL32, ExactSpelling = true, SetLastError = true)] + + public static extern bool CloseHandle(IntPtr handle); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + public static bool IsAspNetCoreModuleLoaded() + { + return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero; + } + + public enum REQUEST_NOTIFICATION_STATUS + { + RQ_NOTIFICATION_CONTINUE, + RQ_NOTIFICATION_PENDING, + RQ_NOTIFICATION_FINISH_REQUEST + } + + public delegate REQUEST_NOTIFICATION_STATUS PFN_REQUEST_HANDLER(IntPtr pInProcessHandler, IntPtr pvRequestContext); + public delegate void PFN_DISCONNECT_HANDLER(IntPtr pvManagedHttpContext); + public delegate bool PFN_SHUTDOWN_HANDLER(IntPtr pvRequestContext); + public delegate REQUEST_NOTIFICATION_STATUS PFN_ASYNC_COMPLETION(IntPtr pvManagedHttpContext, int hr, int bytes); + public delegate REQUEST_NOTIFICATION_STATUS PFN_WEBSOCKET_ASYNC_COMPLETION(IntPtr pInProcessHandler, IntPtr completionInfo, IntPtr pvCompletionContext); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_post_completion(IntPtr pInProcessHandler, int cbBytes); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_set_completion_status(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS rquestNotificationStatus); + + [DllImport(AspNetCoreModuleDll)] + private static extern void http_indicate_completion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus); + + [DllImport(AspNetCoreModuleDll)] + private static extern int register_callbacks(IntPtr pInProcessApplication, + PFN_REQUEST_HANDLER requestCallback, + PFN_SHUTDOWN_HANDLER shutdownCallback, + PFN_DISCONNECT_HANDLER disconnectCallback, + PFN_ASYNC_COMPLETION asyncCallback, + IntPtr pvRequestContext, + IntPtr pvShutdownContext); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_write_response_bytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_flush_response_bytes(IntPtr pInProcessHandler, out bool fCompletionExpected); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe HttpApiTypes.HTTP_REQUEST_V2* http_get_raw_request(IntPtr pInProcessHandler); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_stop_calls_into_managed(IntPtr pInProcessApplication); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_stop_incoming_requests(IntPtr pInProcessApplication); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_disable_buffering(IntPtr pInProcessApplication); + + [DllImport(AspNetCoreModuleDll, CharSet = CharSet.Ansi)] + private static extern int http_set_response_status_code(IntPtr pInProcessHandler, ushort statusCode, string pszReason); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_read_request_bytes(IntPtr pInProcessHandler, byte* pvBuffer, int cbBuffer, out int dwBytesReceived, out bool fCompletionExpected); + + [DllImport(AspNetCoreModuleDll)] + private static extern void http_get_completion_info(IntPtr pCompletionInfo, out int cbBytes, out int hr); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_set_managed_context(IntPtr pInProcessHandler, IntPtr pvManagedContext); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_get_application_properties(ref IISConfigurationData iiConfigData); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_get_server_variable( + IntPtr pInProcessHandler, + [MarshalAs(UnmanagedType.LPStr)] string variableName, + [MarshalAs(UnmanagedType.BStr)] out string value); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_set_server_variable( + IntPtr pInProcessHandler, + [MarshalAs(UnmanagedType.LPStr)] string variableName, + [MarshalAs(UnmanagedType.LPWStr)] string value); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_websockets_read_bytes( + IntPtr pInProcessHandler, + byte* pvBuffer, + int cbBuffer, + PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback, + IntPtr pvCompletionContext, + out int dwBytesReceived, + out bool fCompletionExpected); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_websockets_write_bytes( + IntPtr pInProcessHandler, + HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, + int nChunks, + PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback, + IntPtr pvCompletionContext, + out bool fCompletionExpected); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_enable_websockets(IntPtr pInProcessHandler); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_cancel_io(IntPtr pInProcessHandler); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_close_connection(IntPtr pInProcessHandler); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_response_set_unknown_header(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace); + + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_response_set_known_header(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace); + + [DllImport(AspNetCoreModuleDll)] + private static extern int http_get_authentication_information(IntPtr pInProcessHandler, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token); + + public static void HttpPostCompletion(IntPtr pInProcessHandler, int cbBytes) + { + Validate(http_post_completion(pInProcessHandler, cbBytes)); + } + + public static void HttpSetCompletionStatus(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS rquestNotificationStatus) + { + Validate(http_set_completion_status(pInProcessHandler, rquestNotificationStatus)); + } + + public static void HttpRegisterCallbacks(IntPtr pInProcessApplication, + PFN_REQUEST_HANDLER requestCallback, + PFN_SHUTDOWN_HANDLER shutdownCallback, + PFN_DISCONNECT_HANDLER disconnectCallback, + PFN_ASYNC_COMPLETION asyncCallback, + IntPtr pvRequestContext, + IntPtr pvShutdownContext) + { + Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, disconnectCallback, asyncCallback, pvRequestContext, pvShutdownContext)); + } + + public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected) + { + return http_write_response_bytes(pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected); + } + + public static int HttpFlushResponseBytes(IntPtr pInProcessHandler, out bool fCompletionExpected) + { + return http_flush_response_bytes(pInProcessHandler, out fCompletionExpected); + } + + public static unsafe HttpApiTypes.HTTP_REQUEST_V2* HttpGetRawRequest(IntPtr pInProcessHandler) + { + return http_get_raw_request(pInProcessHandler); + } + + public static void HttpStopCallsIntoManaged(IntPtr pInProcessApplication) + { + Validate(http_stop_calls_into_managed(pInProcessApplication)); + } + + public static void HttpStopIncomingRequests(IntPtr pInProcessApplication) + { + Validate(http_stop_incoming_requests(pInProcessApplication)); + } + + public static void HttpDisableBuffering(IntPtr pInProcessApplication) + { + Validate(http_disable_buffering(pInProcessApplication)); + } + + public static void HttpSetResponseStatusCode(IntPtr pInProcessHandler, ushort statusCode, string pszReason) + { + Validate(http_set_response_status_code(pInProcessHandler, statusCode, pszReason)); + } + + public static unsafe int HttpReadRequestBytes(IntPtr pInProcessHandler, byte* pvBuffer, int cbBuffer, out int dwBytesReceived, out bool fCompletionExpected) + { + return http_read_request_bytes(pInProcessHandler, pvBuffer, cbBuffer, out dwBytesReceived, out fCompletionExpected); + } + + public static void HttpGetCompletionInfo(IntPtr pCompletionInfo, out int cbBytes, out int hr) + { + http_get_completion_info(pCompletionInfo, out cbBytes, out hr); + } + + public static void HttpSetManagedContext(IntPtr pInProcessHandler, IntPtr pvManagedContext) + { + Validate(http_set_managed_context(pInProcessHandler, pvManagedContext)); + } + + public static IISConfigurationData HttpGetApplicationProperties() + { + var iisConfigurationData = new IISConfigurationData(); + Validate(http_get_application_properties(ref iisConfigurationData)); + return iisConfigurationData; + } + + public static bool HttpTryGetServerVariable(IntPtr pInProcessHandler, string variableName, out string value) + { + return http_get_server_variable(pInProcessHandler, variableName, out value) == 0; + } + + public static void HttpSetServerVariable(IntPtr pInProcessHandler, string variableName, string value) + { + Validate(http_set_server_variable(pInProcessHandler, variableName, value)); + } + + public static unsafe int HttpWebsocketsReadBytes( + IntPtr pInProcessHandler, + byte* pvBuffer, + int cbBuffer, + PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback, + IntPtr pvCompletionContext, out int dwBytesReceived, + out bool fCompletionExpected) + { + return http_websockets_read_bytes(pInProcessHandler, pvBuffer, cbBuffer, pfnCompletionCallback, pvCompletionContext, out dwBytesReceived, out fCompletionExpected); + } + + public static unsafe int HttpWebsocketsWriteBytes( + IntPtr pInProcessHandler, + HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, + int nChunks, + PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback, + IntPtr pvCompletionContext, + out bool fCompletionExpected) + { + return http_websockets_write_bytes(pInProcessHandler, pDataChunks, nChunks, pfnCompletionCallback, pvCompletionContext, out fCompletionExpected); + } + + public static void HttpEnableWebsockets(IntPtr pInProcessHandler) + { + Validate(http_enable_websockets(pInProcessHandler)); + } + + public static bool HttpTryCancelIO(IntPtr pInProcessHandler) + { + var hr = http_cancel_io(pInProcessHandler); + // ERROR_NOT_FOUND is expected if async operation finished + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363792(v=vs.85).aspx + // ERROR_INVALID_PARAMETER is expected for "fake" requests like applicationInitialization ones + if (hr == ERROR_NOT_FOUND || hr == ERROR_INVALID_PARAMETER) + { + return false; + } + Validate(hr); + return true; + } + + public static void HttpCloseConnection(IntPtr pInProcessHandler) + { + Validate(http_close_connection(pInProcessHandler)); + } + + public static unsafe void HttpResponseSetUnknownHeader(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace) + { + Validate(http_response_set_unknown_header(pInProcessHandler, pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace)); + } + + public static unsafe void HttpResponseSetKnownHeader(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace) + { + Validate(http_response_set_known_header(pInProcessHandler, headerId, pHeaderValue, length, fReplace)); + } + + public static void HttpGetAuthenticationInformation(IntPtr pInProcessHandler, out string authType, out IntPtr token) + { + Validate(http_get_authentication_information(pInProcessHandler, out authType, out token)); + } + + private static void Validate(int hr) + { + if (hr != HR_OK) + { + throw Marshal.GetExceptionForHR(hr); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Properties/CoreStrings.Designer.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Properties/CoreStrings.Designer.cs new file mode 100644 index 0000000000..639480f4a5 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/Properties/CoreStrings.Designer.cs @@ -0,0 +1,170 @@ +// +namespace Microsoft.AspNetCore.Server.IIS +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class CoreStrings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Server.IIS.CoreStrings", typeof(CoreStrings).GetTypeInfo().Assembly); + + /// + /// Cannot write to response body after connection has been upgraded. + /// + internal static string ResponseStreamWasUpgraded + { + get => GetString("ResponseStreamWasUpgraded"); + } + + /// + /// Cannot write to response body after connection has been upgraded. + /// + internal static string FormatResponseStreamWasUpgraded() + => GetString("ResponseStreamWasUpgraded"); + + /// + /// The response has been aborted due to an unhandled application exception. + /// + internal static string UnhandledApplicationException + { + get => GetString("UnhandledApplicationException"); + } + + /// + /// The response has been aborted due to an unhandled application exception. + /// + internal static string FormatUnhandledApplicationException() + => GetString("UnhandledApplicationException"); + + /// + /// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + /// + internal static string CannotUpgradeNonUpgradableRequest + { + get => GetString("CannotUpgradeNonUpgradableRequest"); + } + + /// + /// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded. + /// + internal static string FormatCannotUpgradeNonUpgradableRequest() + => GetString("CannotUpgradeNonUpgradableRequest"); + + /// + /// IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection. + /// + internal static string UpgradeCannotBeCalledMultipleTimes + { + get => GetString("UpgradeCannotBeCalledMultipleTimes"); + } + + /// + /// IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection. + /// + internal static string FormatUpgradeCannotBeCalledMultipleTimes() + => GetString("UpgradeCannotBeCalledMultipleTimes"); + + /// + /// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + /// + internal static string SynchronousReadsDisallowed + { + get => GetString("SynchronousReadsDisallowed"); + } + + /// + /// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. + /// + internal static string FormatSynchronousReadsDisallowed() + => GetString("SynchronousReadsDisallowed"); + + /// + /// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. + /// + internal static string SynchronousWritesDisallowed + { + get => GetString("SynchronousWritesDisallowed"); + } + + /// + /// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. + /// + internal static string FormatSynchronousWritesDisallowed() + => GetString("SynchronousWritesDisallowed"); + + /// + /// Cannot write to the response body, the response has completed. + /// + internal static string WritingToResponseBodyAfterResponseCompleted + { + get => GetString("WritingToResponseBodyAfterResponseCompleted"); + } + + /// + /// Cannot write to the response body, the response has completed. + /// + internal static string FormatWritingToResponseBodyAfterResponseCompleted() + => GetString("WritingToResponseBodyAfterResponseCompleted"); + + /// + /// The connection was aborted by the application. + /// + internal static string ConnectionAbortedByApplication + { + get => GetString("ConnectionAbortedByApplication"); + } + + /// + /// The connection was aborted by the application. + /// + internal static string FormatConnectionAbortedByApplication() + => GetString("ConnectionAbortedByApplication"); + + /// + /// The connection or stream was aborted because a write operation was aborted with a CancellationToken. + /// + internal static string ConnectionOrStreamAbortedByCancellationToken + { + get => GetString("ConnectionOrStreamAbortedByCancellationToken"); + } + + /// + /// The connection or stream was aborted because a write operation was aborted with a CancellationToken. + /// + internal static string FormatConnectionOrStreamAbortedByCancellationToken() + => GetString("ConnectionOrStreamAbortedByCancellationToken"); + + /// + /// {name} cannot be set because the response has already started. + /// + internal static string ParameterReadOnlyAfterResponseStarted + { + get => GetString("ParameterReadOnlyAfterResponseStarted"); + } + + /// + /// {name} cannot be set because the response has already started. + /// + internal static string FormatParameterReadOnlyAfterResponseStarted(object name) + => string.Format(CultureInfo.CurrentCulture, GetString("ParameterReadOnlyAfterResponseStarted", "name"), name); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs new file mode 100644 index 0000000000..146f9a50f5 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.IIS; +using Microsoft.AspNetCore.Server.IIS.Core; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class WebHostBuilderIISExtensions + { + /// + /// Configures the port and base path the server should listen on when running behind AspNetCoreModule. + /// The app will also be configured to capture startup errors. + /// + /// + /// + public static IWebHostBuilder UseIIS(this IWebHostBuilder hostBuilder) + { + if (hostBuilder == null) + { + throw new ArgumentNullException(nameof(hostBuilder)); + } + + // Check if in process + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && NativeMethods.IsAspNetCoreModuleLoaded()) + { + hostBuilder.CaptureStartupErrors(true); + + var iisConfigData = NativeMethods.HttpGetApplicationProperties(); + // Trim trailing slash to be consistent with other servers + var contentRoot = iisConfigData.pwzFullApplicationPath.TrimEnd(Path.DirectorySeparatorChar); + hostBuilder.UseContentRoot(contentRoot); + return hostBuilder.ConfigureServices( + services => { + services.AddSingleton(new IISNativeApplication(iisConfigData.pNativeApplication)); + services.AddSingleton(); + services.AddSingleton(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath)); + services.AddAuthenticationCore(); + services.Configure( + options => { options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled; } + ); + }); + } + + return hostBuilder; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj index 9ec5253b39..faae91cdbf 100644 --- a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core components for working with the IIS AspNetCoreModule. @@ -21,9 +21,9 @@ - + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.targets b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.targets new file mode 100644 index 0000000000..a5f7fc6088 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.targets @@ -0,0 +1,7 @@ + + + + AspNetCoreModuleV2 + + + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs index 4ecb5018a2..02fc967f1a 100644 --- a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.InteropServices; -using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.IISIntegration { diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs new file mode 100644 index 0000000000..8fddbaede6 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + /// + /// Factory to create an appropriate deployer based on . + /// + public class IISApplicationDeployerFactory + { + /// + /// Creates a deployer instance based on settings in . + /// + /// + /// + /// + public static ApplicationDeployer Create(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + { + if (deploymentParameters == null) + { + throw new ArgumentNullException(nameof(deploymentParameters)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + switch (deploymentParameters.ServerType) + { + case ServerType.IISExpress: + return new IISExpressDeployer(deploymentParameters, loggerFactory); + case ServerType.IIS: + return new IISDeployer(deploymentParameters, loggerFactory); + default: + return ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory); + } + } + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Https.config b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Http.config similarity index 99% rename from src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Https.config rename to src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Http.config index e16fd734b7..8c74496e12 100644 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Https.config +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Http.config @@ -5,7 +5,7 @@ For schema documentation, see %IIS_BIN%\config\schema\IIS_schema.xml. - + Please make a backup of this file before making any changes to it. NOTE: The following environment variables are available to be used @@ -25,20 +25,20 @@ The section controls the registration of sections. Section is the basic unit of deployment, locking, searching and containment for configuration settings. - + Every section belongs to one section group. A section group is a container of logically-related sections. - + Sections cannot be nested. Section groups may be nested. - +
- + The recommended way to unlock sections is by using a location tag: @@ -155,12 +155,12 @@ - + - + @@ -176,7 +176,6 @@ - @@ -253,7 +252,6 @@ - @@ -315,7 +313,7 @@ - + @@ -334,7 +332,7 @@ - + @@ -876,10 +874,6 @@ - - - - @@ -887,6 +881,7 @@ + @@ -894,6 +889,7 @@ + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs new file mode 100644 index 0000000000..4f2e525e72 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs @@ -0,0 +1,450 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.Extensions.Logging; +using Microsoft.Web.Administration; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + /// + /// Deployer for IIS. + /// + public class IISDeployer : IISDeployerBase + { + private const string DetailedErrorsEnvironmentVariable = "ASPNETCORE_DETAILEDERRORS"; + + private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(60); + private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200); + + private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource(); + + private string _configPath; + private string _debugLogFile; + + public Process HostProcess { get; set; } + + public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(new IISDeploymentParameters(deploymentParameters), loggerFactory) + { + } + + public IISDeployer(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(deploymentParameters, loggerFactory) + { + } + + public override void Dispose() + { + Dispose(gracefulShutdown: false); + } + + public override void Dispose(bool gracefulShutdown) + { + Stop(); + + TriggerHostShutdown(_hostShutdownToken); + + GetLogsFromFile(); + + CleanPublishedOutput(); + InvokeUserApplicationCleanup(); + + StopTimer(); + } + + public override Task DeployAsync() + { + using (Logger.BeginScope("Deployment")) + { + StartTimer(); + + if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) + { + DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config"); + } + + // For now, only support using published output + DeploymentParameters.PublishApplicationBeforeDeployment = true; + // Move ASPNETCORE_DETAILEDERRORS to web config env variables + if (IISDeploymentParameters.EnvironmentVariables.ContainsKey(DetailedErrorsEnvironmentVariable)) + { + IISDeploymentParameters.WebConfigBasedEnvironmentVariables[DetailedErrorsEnvironmentVariable] = + IISDeploymentParameters.EnvironmentVariables[DetailedErrorsEnvironmentVariable]; + + IISDeploymentParameters.EnvironmentVariables.Remove(DetailedErrorsEnvironmentVariable); + } + // Do not override settings set on parameters + if (!IISDeploymentParameters.HandlerSettings.ContainsKey("debugLevel") && + !IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile")) + { + _debugLogFile = Path.GetTempFileName(); + IISDeploymentParameters.HandlerSettings["debugLevel"] = "file"; + IISDeploymentParameters.HandlerSettings["debugFile"] = _debugLogFile; + } + + DotnetPublish(); + var contentRoot = DeploymentParameters.PublishedApplicationRootPath; + + RunWebConfigActions(contentRoot); + + var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint); + StartIIS(uri, contentRoot); + + // Warm up time for IIS setup. + Logger.LogInformation("Successfully finished IIS application directory setup."); + return Task.FromResult(new IISDeploymentResult( + LoggerFactory, + IISDeploymentParameters, + applicationBaseUri: uri.ToString(), + contentRoot: contentRoot, + hostShutdownToken: _hostShutdownToken.Token, + hostProcess: HostProcess + )); + } + } + + protected override IEnumerable> GetWebConfigActions() + { + yield return WebConfigHelpers.AddOrModifyAspNetCoreSection( + key: "hostingModel", + value: DeploymentParameters.HostingModel.ToString()); + + yield return (element, _) => { + var aspNetCore = element + .Descendants("system.webServer") + .Single() + .GetOrAdd("aspNetCore"); + + // Expand path to dotnet because IIS process would not inherit PATH variable + if (aspNetCore.Attribute("processPath")?.Value.StartsWith("dotnet") == true) + { + aspNetCore.SetAttributeValue("processPath", DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture)); + } + }; + + yield return WebConfigHelpers.AddOrModifyHandlerSection( + key: "modules", + value: DeploymentParameters.AncmVersion.ToString()); + + foreach (var action in base.GetWebConfigActions()) + { + yield return action; + } + } + + private void GetLogsFromFile() + { + try + { + + // Handle cases where debug file is redirected by test + var debugLogLocations = new List(); + if (IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile")) + { + debugLogLocations.Add(IISDeploymentParameters.HandlerSettings["debugFile"]); + } + + if (DeploymentParameters.EnvironmentVariables.ContainsKey("ASPNETCORE_MODULE_DEBUG_FILE")) + { + debugLogLocations.Add(DeploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"]); + } + + // default debug file name + debugLogLocations.Add("aspnetcore-debug.log"); + + foreach (var debugLogLocation in debugLogLocations) + { + if (string.IsNullOrEmpty(debugLogLocation)) + { + continue; + } + + var file = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, debugLogLocation); + if (File.Exists(file)) + { + var lines = File.ReadAllLines(file); + if (!lines.Any()) + { + Logger.LogInformation($"Debug log file {file} found but was empty"); + continue; + } + + foreach (var line in lines) + { + Logger.LogInformation(line); + } + return; + } + } + // ANCM V1 does not support logs + if (DeploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2) + { + throw new InvalidOperationException($"Unable to find non-empty debug log files. Tried: {string.Join(", ", debugLogLocations)}"); + } + } + finally + { + if (File.Exists(_debugLogFile)) + { + File.Delete(_debugLogFile); + } + } + } + + public void StartIIS(Uri uri, string contentRoot) + { + // Backup currently deployed apphost.config file + using (Logger.BeginScope("StartIIS")) + { + var port = uri.Port; + if (port == 0) + { + throw new NotSupportedException("Cannot set port 0 for IIS."); + } + + AddTemporaryAppHostConfig(contentRoot, port); + + WaitUntilSiteStarted(); + } + } + + private void WaitUntilSiteStarted() + { + ServiceController serviceController = new ServiceController("w3svc"); + Logger.LogInformation("W3SVC status " + serviceController.Status); + + if (serviceController.Status != ServiceControllerStatus.Running && + serviceController.Status != ServiceControllerStatus.StartPending) + { + Logger.LogInformation("Starting W3SVC"); + + serviceController.Start(); + serviceController.WaitForStatus(ServiceControllerStatus.Running, _timeout); + } + + RetryServerManagerAction(serverManager => + { + var site = serverManager.Sites.Single(); + var appPool = serverManager.ApplicationPools.Single(); + + if (appPool.State != ObjectState.Started && appPool.State != ObjectState.Starting) + { + var state = appPool.Start(); + Logger.LogInformation($"Starting pool, state: {state.ToString()}"); + } + + if (site.State != ObjectState.Started && site.State != ObjectState.Starting) + { + var state = site.Start(); + Logger.LogInformation($"Starting site, state: {state.ToString()}"); + } + + if (site.State != ObjectState.Started) + { + throw new InvalidOperationException("Site not started yet"); + } + + var workerProcess = appPool.WorkerProcesses.SingleOrDefault(); + if (workerProcess == null) + { + throw new InvalidOperationException("Site is started but no worked process found"); + } + + HostProcess = Process.GetProcessById(workerProcess.ProcessId); + + // Ensure w3wp.exe is killed if test process termination is non-graceful. + // Prevents locked files when stop debugging unit test. + ProcessTracker.Add(HostProcess); + + // cache the process start time for verifying log file name. + var _ = HostProcess.StartTime; + + Logger.LogInformation("Site has started."); + }); + } + + private void AddTemporaryAppHostConfig(string contentRoot, int port) + { + _configPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D")); + var appHostConfigPath = Path.Combine(_configPath, "applicationHost.config"); + Directory.CreateDirectory(_configPath); + var config = XDocument.Parse(DeploymentParameters.ServerConfigTemplateContent ?? File.ReadAllText("IIS.config")); + + ConfigureAppHostConfig(config.Root, contentRoot, port); + + config.Save(appHostConfigPath); + + RetryServerManagerAction(serverManager => + { + var redirectionConfiguration = serverManager.GetRedirectionConfiguration(); + var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection"); + + redirectionSection.Attributes["enabled"].Value = true; + redirectionSection.Attributes["path"].Value = _configPath; + + serverManager.CommitChanges(); + }); + } + + private void ConfigureAppHostConfig(XElement config, string contentRoot, int port) + { + ConfigureModuleAndBinding(config, contentRoot, port); + + // In IISExpress system.webServer/modules in under location element + config + .RequiredElement("system.webServer") + .RequiredElement("modules") + .GetOrAdd("add", "name", DeploymentParameters.AncmVersion.ToString()); + + var pool = config + .RequiredElement("system.applicationHost") + .RequiredElement("applicationPools") + .RequiredElement("add"); + + if (DeploymentParameters.EnvironmentVariables.Any()) + { + var environmentVariables = pool + .GetOrAdd("environmentVariables"); + + foreach (var tuple in DeploymentParameters.EnvironmentVariables) + { + environmentVariables + .GetOrAdd("add", "name", tuple.Key) + .SetAttributeValue("value", tuple.Value); + } + + } + + RunServerConfigActions(config, contentRoot); + } + + private void Stop() + { + try + { + RetryServerManagerAction(serverManager => + { + var site = serverManager.Sites.SingleOrDefault(); + if (site == null) + { + throw new InvalidOperationException("Site not found"); + } + + if (site.State != ObjectState.Stopped && site.State != ObjectState.Stopping) + { + var state = site.Stop(); + Logger.LogInformation($"Stopping site, state: {state.ToString()}"); + } + + var appPool = serverManager.ApplicationPools.SingleOrDefault(); + if (appPool == null) + { + throw new InvalidOperationException("Application pool not found"); + } + + if (appPool.State != ObjectState.Stopped && appPool.State != ObjectState.Stopping) + { + var state = appPool.Stop(); + Logger.LogInformation($"Stopping pool, state: {state.ToString()}"); + } + + if (site.State != ObjectState.Stopped) + { + throw new InvalidOperationException("Site not stopped yet"); + } + + try + { + if (appPool.WorkerProcesses != null && + appPool.WorkerProcesses.Any(wp => + wp.State == WorkerProcessState.Running || + wp.State == WorkerProcessState.Stopping)) + { + throw new InvalidOperationException("WorkerProcess not stopped yet"); + } + + } + // If WAS was stopped for some reason appPool.WorkerProcesses + // would throw UnauthorizedAccessException. + // check if it's the case and continue shutting down deployer + catch (UnauthorizedAccessException) + { + var serviceController = new ServiceController("was"); + if (serviceController.Status != ServiceControllerStatus.Stopped) + { + throw; + } + } + + if (!HostProcess.HasExited) + { + throw new InvalidOperationException("Site is stopped but host process is not"); + } + + Logger.LogInformation($"Site has stopped successfully."); + }); + } + finally + { + // Undo redirection.config changes unconditionally + RetryServerManagerAction(serverManager => + { + var redirectionConfiguration = serverManager.GetRedirectionConfiguration(); + var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection"); + + redirectionSection.Attributes["enabled"].Value = false; + + serverManager.CommitChanges(); + if (Directory.Exists(_configPath)) + { + Directory.Delete(_configPath, true); + } + }); + } + } + + private void RetryServerManagerAction(Action action) + { + List exceptions = null; + var sw = Stopwatch.StartNew(); + int retryCount = 0; + + while (sw.Elapsed < _timeout) + { + try + { + using (var serverManager = new ServerManager()) + { + action(serverManager); + } + + return; + } + catch (Exception ex) + { + if (exceptions == null) + { + exceptions = new List(); + } + + exceptions.Add(ex); + } + + retryCount++; + Thread.Sleep(_retryDelay); + } + + throw new AggregateException($"Operation did not succeed after {retryCount} retries", exceptions.ToArray()); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs new file mode 100644 index 0000000000..d0745b4f01 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs @@ -0,0 +1,157 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public abstract class IISDeployerBase : ApplicationDeployer + { + public IISDeploymentParameters IISDeploymentParameters { get; } + + public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(deploymentParameters, loggerFactory) + { + IISDeploymentParameters = deploymentParameters; + } + + protected void RunWebConfigActions(string contentRoot) + { + var actions = GetWebConfigActions(); + if (!actions.Any()) + { + return; + } + + if (!DeploymentParameters.PublishApplicationBeforeDeployment) + { + throw new InvalidOperationException("Cannot modify web.config file if no published output."); + } + + var path = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); + var webconfig = XDocument.Load(path); + + foreach (var action in actions) + { + action.Invoke(webconfig.Root, contentRoot); + } + + webconfig.Save(path); + } + + protected virtual IEnumerable> GetWebConfigActions() + { + if (IISDeploymentParameters.HandlerSettings.Any()) + { + yield return AddHandlerSettings; + } + + if (IISDeploymentParameters.WebConfigBasedEnvironmentVariables.Any()) + { + yield return AddWebConfigEnvironmentVariables; + } + + foreach (var action in IISDeploymentParameters.WebConfigActionList) + { + yield return action; + } + } + + protected virtual IEnumerable> GetServerConfigActions() + { + foreach (var action in IISDeploymentParameters.ServerConfigActionList) + { + yield return action; + } + } + + public void RunServerConfigActions(XElement config, string contentRoot) + { + foreach (var action in GetServerConfigActions()) + { + action.Invoke(config, contentRoot); + } + } + + protected string GetAncmLocation(AncmVersion version) + { + var ancmDllName = version == AncmVersion.AspNetCoreModuleV2 ? "aspnetcorev2.dll" : "aspnetcore.dll"; + var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{ancmDllName}" : $@"x86\{ancmDllName}"; + var ancmFile = Path.Combine(AppContext.BaseDirectory, arch); + if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile))) + { + ancmFile = Path.Combine(AppContext.BaseDirectory, ancmDllName); + if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile))) + { + throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile); + } + } + + return ancmFile; + } + + private void AddWebConfigEnvironmentVariables(XElement element, string contentRoot) + { + var environmentVariables = element + .Descendants("system.webServer") + .Single() + .RequiredElement("aspNetCore") + .GetOrAdd("environmentVariables"); + + foreach (var envVar in IISDeploymentParameters.WebConfigBasedEnvironmentVariables) + { + environmentVariables.GetOrAdd("environmentVariable", "name", envVar.Key) + .SetAttributeValue("value", envVar.Value); + } + } + + private void AddHandlerSettings(XElement element, string contentRoot) + { + var handlerSettings = element + .Descendants("system.webServer") + .Single() + .RequiredElement("aspNetCore") + .GetOrAdd("handlerSettings"); + + foreach (var handlerSetting in IISDeploymentParameters.HandlerSettings) + { + handlerSettings.GetOrAdd("handlerSetting", "name", handlerSetting.Key) + .SetAttributeValue("value", handlerSetting.Value); + } + } + + protected void ConfigureModuleAndBinding(XElement config, string contentRoot, int port) + { + var siteElement = config + .RequiredElement("system.applicationHost") + .RequiredElement("sites") + .RequiredElement("site"); + + siteElement + .RequiredElement("application") + .RequiredElement("virtualDirectory") + .SetAttributeValue("physicalPath", contentRoot); + + siteElement + .RequiredElement("bindings") + .RequiredElement("binding") + .SetAttributeValue("bindingInformation", $":{port}:localhost"); + + var ancmVersion = DeploymentParameters.AncmVersion.ToString(); + config + .RequiredElement("system.webServer") + .RequiredElement("globalModules") + .GetOrAdd("add", "name", ancmVersion) + .SetAttributeValue("image", GetAncmLocation(DeploymentParameters.AncmVersion)); + } + + public abstract void Dispose(bool gracefulShutdown); + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs new file mode 100644 index 0000000000..90f76fd8c2 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs @@ -0,0 +1,240 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public static class IISDeploymentParameterExtensions + { + public static void AddDebugLogToWebConfig(this IISDeploymentParameters parameters, string filename) + { + parameters.HandlerSettings["debugLevel"] = "file"; + parameters.HandlerSettings["debugFile"] = filename; + } + + public static void AddServerConfigAction(this IISDeploymentParameters parameters, Action action) + { + parameters.ServerConfigActionList.Add((config, _) => action(config)); + } + + public static void AddServerConfigAction(this IISDeploymentParameters parameters, Action action) + { + parameters.ServerConfigActionList.Add(action); + } + + public static void AddHttpsToServerConfig(this IISDeploymentParameters parameters) + { + parameters.AddServerConfigAction( + element => + { + element.Descendants("binding") + .Single() + .SetAttributeValue("protocol", "https"); + + element.Descendants("access") + .Single() + .SetAttributeValue("sslFlags", "Ssl, SslNegotiateCert"); + }); + } + + public static void SetWindowsAuth(this IISDeploymentParameters parameters, bool enabled = true) + { + parameters.EnsureSection("windowsAuthentication", "system.webServer", "security", "windowsAuthentication"); + parameters.EnableModule("WindowsAuthenticationModule", "%IIS_BIN%\\authsspi.dll"); + + parameters.AddServerConfigAction( + element => + { + var windowsAuthentication = element + .RequiredElement("system.webServer") + .RequiredElement("security") + .RequiredElement("authentication") + .GetOrAdd("windowsAuthentication"); + + windowsAuthentication.SetAttributeValue("enabled", enabled); + var providers = windowsAuthentication.GetOrAdd("providers"); + providers.GetOrAdd("add", "value", "Negotiate"); + providers.GetOrAdd("add", "value", "NTLM"); + }); + } + + public static void SetAnonymousAuth(this IISDeploymentParameters parameters, bool enabled = true) + { + parameters.AddServerConfigAction( + element => + { + element + .RequiredElement("system.webServer") + .RequiredElement("security") + .RequiredElement("authentication") + .GetOrAdd("anonymousAuthentication") + .SetAttributeValue("enabled", enabled); + }); + } + + public static void SetBasicAuth(this IISDeploymentParameters parameters, bool enabled = true) + { + parameters.EnableModule("BasicAuthenticationModule", "%IIS_BIN%\\authbas.dll"); + + parameters.AddServerConfigAction( + element => + { + element + .RequiredElement("system.webServer") + .RequiredElement("security") + .RequiredElement("authentication") + .GetOrAdd("basicAuthentication") + .SetAttributeValue("enabled", enabled); + }); + } + + public static void EnsureSection(this IISDeploymentParameters parameters, string name, params string[] path) + { + parameters.ServerConfigActionList.Add( + (config, _) => { + + var element = config + .RequiredElement("configSections"); + + foreach (var s in path) + { + element = element.GetOrAdd("sectionGroup", "name", s); + } + + element.GetOrAdd("section", "name", "applicationInitialization") + .SetAttributeValue("overrideModeDefault", "Allow"); + }); + } + + public static void EnableLogging(this IISDeploymentParameters deploymentParameters, string path) + { + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogEnabled", "true")); + + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine(path, "std"))); + } + + public static void EnableFreb(this IISDeploymentParameters deploymentParameters, string verbosity, string folderPath) + { + if (!deploymentParameters.PublishApplicationBeforeDeployment) + { + throw new InvalidOperationException("Testing freb requires site to be published."); + } + + deploymentParameters.EnableModule("FailedRequestsTracingModule", "%IIS_BIN%\\iisfreb.dll"); + + // Set the TraceFailedRequestsSection to listend to ANCM events + deploymentParameters.ServerConfigActionList.Add( + (element, _) => + { + var webServerElement = element + .RequiredElement("system.webServer"); + + var addElement = webServerElement + .GetOrAdd("tracing") + .GetOrAdd("traceFailedRequests") + .GetOrAdd("add"); + + addElement.SetAttributeValue("path", "*"); + + addElement.GetOrAdd("failureDefinitions") + .SetAttributeValue("statusCodes", "200-999"); + + var traceAreasElement = addElement + .GetOrAdd("traceAreas"); + var innerAddElement = traceAreasElement.GetOrAdd("add", "provider", "WWW Server"); + + innerAddElement.SetAttributeValue("areas", "ANCM"); + innerAddElement.SetAttributeValue("verbosity", verbosity); + }); + + // Set the ANCM traceProviderDefinition to 65536 + deploymentParameters.ServerConfigActionList.Add( + (element, _) => + { + var webServerElement = element + .RequiredElement("system.webServer"); + + var traceProviderDefinitionsElement = webServerElement + .GetOrAdd("tracing") + .GetOrAdd("traceProviderDefinitions"); + + var innerAddElement = traceProviderDefinitionsElement.GetOrAdd("add", "name", "WWW Server"); + + innerAddElement.SetAttributeValue("name", "WWW Server"); + innerAddElement.SetAttributeValue("guid", "{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}"); + + var areasElement = innerAddElement.GetOrAdd("areas"); + var iae = areasElement.GetOrAdd("add", "name", "ANCM"); + + iae.SetAttributeValue("value", "65536"); + }); + + // Set the freb directory to the published app directory. + deploymentParameters.ServerConfigActionList.Add( + (element, contentRoot) => + { + var traceFailedRequestsElement = element + .RequiredElement("system.applicationHost") + .Element("sites") + .Element("siteDefaults") + .Element("traceFailedRequestsLogging"); + traceFailedRequestsElement.SetAttributeValue("directory", folderPath); + traceFailedRequestsElement.SetAttributeValue("enabled", "true"); + traceFailedRequestsElement.SetAttributeValue("maxLogFileSizeKB", "1024"); + }); + } + + public static void TransformPath(this IISDeploymentParameters parameters, Func transformation) + { + parameters.WebConfigActionList.Add( + (config, contentRoot) => + { + var aspNetCoreElement = config.Descendants("aspNetCore").Single(); + aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot)); + }); + } + + public static void TransformArguments(this IISDeploymentParameters parameters, Func transformation) + { + parameters.WebConfigActionList.Add( + (config, contentRoot) => + { + var aspNetCoreElement = config.Descendants("aspNetCore").Single(); + aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot)); + }); + } + + public static void EnableModule(this IISDeploymentParameters parameters, string moduleName, string modulePath) + { + if (parameters.ServerType == ServerType.IIS) + { + modulePath = modulePath.Replace("%IIS_BIN%", "%windir%\\System32\\inetsrv"); + } + + parameters.ServerConfigActionList.Add( + (element, _) => + { + var webServerElement = element + .RequiredElement("system.webServer"); + + webServerElement + .RequiredElement("globalModules") + .GetOrAdd("add", "name", moduleName) + .SetAttributeValue("image", modulePath); + + (webServerElement.Element("modules") ?? + element + .Element("location") + .RequiredElement("system.webServer") + .RequiredElement("modules")) + .GetOrAdd("add", "name", moduleName); + }); + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs new file mode 100644 index 0000000000..3b2dfc4804 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs @@ -0,0 +1,52 @@ +// 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.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class IISDeploymentParameters : DeploymentParameters + { + public IISDeploymentParameters() : base() + { + } + + public IISDeploymentParameters(TestVariant variant) + : base(variant) + { + } + + public IISDeploymentParameters( + string applicationPath, + ServerType serverType, + RuntimeFlavor runtimeFlavor, + RuntimeArchitecture runtimeArchitecture) + : base(applicationPath, serverType, runtimeFlavor, runtimeArchitecture) + { + } + + public IISDeploymentParameters(DeploymentParameters parameters) + : base(parameters) + { + if (parameters is IISDeploymentParameters) + { + var tempParameters = (IISDeploymentParameters)parameters; + WebConfigActionList = tempParameters.WebConfigActionList; + ServerConfigActionList = tempParameters.ServerConfigActionList; + WebConfigBasedEnvironmentVariables = tempParameters.WebConfigBasedEnvironmentVariables; + HandlerSettings = tempParameters.HandlerSettings; + } + } + + public IList> WebConfigActionList { get; } = new List>(); + + public IList> ServerConfigActionList { get; } = new List>(); + + public IDictionary WebConfigBasedEnvironmentVariables { get; set; } = new Dictionary(); + + public IDictionary HandlerSettings { get; set; } = new Dictionary(); + + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs new file mode 100644 index 0000000000..814a778c16 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs @@ -0,0 +1,43 @@ +// 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.Diagnostics; +using System.Net.Http; +using System.Threading; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class IISDeploymentResult : DeploymentResult + { + public ILogger Logger { get; set; } + public Process HostProcess { get; } + + public IISDeploymentResult(ILoggerFactory loggerFactory, + IISDeploymentParameters deploymentParameters, + string applicationBaseUri, + string contentRoot, + CancellationToken hostShutdownToken, + Process hostProcess) + : base(loggerFactory, + deploymentParameters, + applicationBaseUri, + contentRoot, + hostShutdownToken) + { + HostProcess = hostProcess; + Logger = loggerFactory.CreateLogger(deploymentParameters.SiteName); + HttpClient = CreateClient(new HttpClientHandler()); + } + + public HttpClient CreateClient(HttpMessageHandler messageHandler) + { + return new HttpClient(new LoggingHandler(messageHandler, Logger)) + { + BaseAddress = base.HttpClient.BaseAddress + }; + } + + public new HttpClient HttpClient { get; set; } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs new file mode 100644 index 0000000000..8fe8c66585 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs @@ -0,0 +1,497 @@ +// 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.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + /// + /// Deployment helper for IISExpress. + /// + public class IISExpressDeployer : IISDeployerBase + { + private const string IISExpressRunningMessage = "IIS Express is running."; + private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings"; + private const string UnableToStartIISExpressMessage = "Unable to start iisexpress."; + private const int MaximumAttempts = 5; + private readonly TimeSpan ShutdownTimeSpan = Debugger.IsAttached ? TimeSpan.FromMinutes(60) : TimeSpan.FromMinutes(1); + private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$"); + + private Process _hostProcess; + + public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(new IISDeploymentParameters(deploymentParameters), loggerFactory) + { + } + + public IISExpressDeployer(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(deploymentParameters, loggerFactory) + { + } + + public override async Task DeployAsync() + { + using (Logger.BeginScope("Deployment")) + { + // Start timer + StartTimer(); + + // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/net461/ + // and contentRoot points to the project directory so you get things like static assets. + // For a published app both point to the publish directory. + var dllRoot = CheckIfPublishIsRequired(); + var contentRoot = string.Empty; + if (DeploymentParameters.PublishApplicationBeforeDeployment) + { + DotnetPublish(); + contentRoot = DeploymentParameters.PublishedApplicationRootPath; + } + else + { + // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable. + // Update processPath and arguments for our current scenario + contentRoot = DeploymentParameters.ApplicationPath; + + var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ".exe"; + var entryPoint = Path.Combine(dllRoot, DeploymentParameters.ApplicationName + executableExtension); + + var executableName = string.Empty; + var executableArgs = string.Empty; + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) + { + executableName = GetDotNetExeForArchitecture(); + executableArgs = entryPoint; + } + else + { + executableName = entryPoint; + } + + Logger.LogInformation("Executing: {exe} {args}", executableName, executableArgs); + DeploymentParameters.EnvironmentVariables["LAUNCHER_PATH"] = executableName; + DeploymentParameters.EnvironmentVariables["LAUNCHER_ARGS"] = executableArgs; + + // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead. + Logger.LogInformation("ContentRoot: {path}", DeploymentParameters.ApplicationPath); + DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath; + } + + RunWebConfigActions(contentRoot); + + var testUri = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint); + + // Launch the host process. + var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot); + + Logger.LogInformation("Application ready at URL: {appUrl}", actualUri); + + // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath. + + return new IISDeploymentResult( + LoggerFactory, + IISDeploymentParameters, + applicationBaseUri: actualUri.ToString(), + contentRoot: contentRoot, + hostShutdownToken: hostExitToken, + hostProcess: _hostProcess); + } + } + + private string CheckIfPublishIsRequired() + { + var targetFramework = DeploymentParameters.TargetFramework; + + // IISIntegration uses this layout + var dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.RuntimeArchitecture.ToString(), + DeploymentParameters.Configuration, targetFramework); + + if (!Directory.Exists(dllRoot)) + { + // Most repos use this layout + dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework); + + if (!Directory.Exists(dllRoot)) + { + // The bits we need weren't pre-compiled, compile on publish + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr + && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) + { + // x64 is the default. Publish to rebuild for the right bitness + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + } + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Standalone) + { + // Publish is always required to get the correct standalone files in the output directory + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + + return dllRoot; + } + + private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot) + { + using (Logger.BeginScope("StartIISExpress")) + { + var port = uri.Port; + if (port == 0) + { + port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort(); + } + + Logger.LogInformation("Attempting to start IIS Express on port: {port}", port); + PrepareConfig(contentRoot, port); + + var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ? + string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) : + string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation); + + var iisExpressPath = GetIISExpressPath(); + + for (var attempt = 0; attempt < MaximumAttempts; attempt++) + { + Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters); + + var startInfo = new ProcessStartInfo + { + FileName = iisExpressPath, + Arguments = parameters, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + // VS sets current directory to C:\Program Files\IIS Express + WorkingDirectory = Path.GetDirectoryName(iisExpressPath) + }; + + AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables); + + Uri url = null; + var started = new TaskCompletionSource(); + + var process = new Process() { StartInfo = startInfo }; + process.OutputDataReceived += (sender, dataArgs) => + { + if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage)) + { + // We completely failed to start and we don't really know why + started.TrySetException(new InvalidOperationException("Failed to start IIS Express")); + } + else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage)) + { + started.TrySetResult(false); + } + else if (string.Equals(dataArgs.Data, IISExpressRunningMessage)) + { + started.TrySetResult(true); + } + else if (!string.IsNullOrEmpty(dataArgs.Data)) + { + var m = UrlDetectorRegex.Match(dataArgs.Data); + if (m.Success) + { + url = new Uri(m.Groups["url"].Value); + } + } + }; + + process.EnableRaisingEvents = true; + var hostExitTokenSource = new CancellationTokenSource(); + process.Exited += (sender, e) => + { + Logger.LogInformation("iisexpress Process {pid} shut down", process.Id); + + // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want + started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}")); + + TriggerHostShutdown(hostExitTokenSource); + }; + process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger); + Logger.LogInformation("iisexpress Process {pid} started", process.Id); + + if (process.HasExited) + { + Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode); + throw new Exception("Failed to start host"); + } + + // Wait for the app to start + // The timeout here is large, because we don't know how long the test could need + // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build + // just in case we missed one -anurse + if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10))) + { + Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port); + + // Wait for the process to exit and try again + process.WaitForExit(30 * 1000); + await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up + } + else + { + _hostProcess = process; + + // Ensure iisexpress.exe is killed if test process termination is non-graceful. + // Prevents locked files when stop debugging unit test. + ProcessTracker.Add(_hostProcess); + + // cache the process start time for verifying log file name. + var _ = _hostProcess.StartTime; + + Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port); + return (url: url, hostExitToken: hostExitTokenSource.Token); + } + } + + var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port"; + Logger.LogError(message); + throw new TimeoutException(message); + } + } + + private void PrepareConfig(string contentRoot, int port) + { + var serverConfig = DeploymentParameters.ServerConfigTemplateContent;; + // Config is required. If not present then fall back to one we carry with us. + if (string.IsNullOrEmpty(serverConfig)) + { + using (var stream = GetType().Assembly.GetManifestResourceStream("Microsoft.AspNetCore.Server.IntegrationTesting.IIS.Http.config")) + using (var reader = new StreamReader(stream)) + { + serverConfig = reader.ReadToEnd(); + } + } + + XDocument config = XDocument.Parse(serverConfig); + // Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config + // We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo. + + config.Root + .RequiredElement("location") + .RequiredElement("system.webServer") + .RequiredElement("modules") + .GetOrAdd("add", "name", DeploymentParameters.AncmVersion.ToString()); + + ConfigureModuleAndBinding(config.Root, contentRoot, port); + + var webConfigPath = Path.Combine(contentRoot, "web.config"); + if (!DeploymentParameters.PublishApplicationBeforeDeployment && !File.Exists(webConfigPath)) + { + // The elements normally in the web.config are in the applicationhost.config for unpublished apps. + AddAspNetCoreElement(config.Root); + } + + RunServerConfigActions(config.Root, contentRoot); + serverConfig = config.ToString(); + + DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); + Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); + + File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig); + } + + private void AddAspNetCoreElement(XElement config) + { + var aspNetCore = config + .RequiredElement("system.webServer") + .GetOrAdd("aspNetCore"); + + aspNetCore.SetAttributeValue("hostingModel", DeploymentParameters.HostingModel.ToString()); + aspNetCore.SetAttributeValue("arguments", "%LAUNCHER_ARGS%"); + aspNetCore.SetAttributeValue("processPath", "%LAUNCHER_PATH%"); + + var handlers = config + .RequiredElement("location") + .RequiredElement("system.webServer") + .RequiredElement("handlers"); + + var aspNetCoreHandler = handlers + .GetOrAdd("add", "name", "aspNetCore"); + + aspNetCoreHandler.SetAttributeValue("path", "*"); + aspNetCoreHandler.SetAttributeValue("verb", "*"); + aspNetCoreHandler.SetAttributeValue("modules", DeploymentParameters.AncmVersion.ToString()); + aspNetCoreHandler.SetAttributeValue("resourceType", "Unspecified"); + // Make aspNetCore handler first + aspNetCoreHandler.Remove(); + handlers.AddFirst(aspNetCoreHandler); + } + + protected override IEnumerable> GetWebConfigActions() + { + if (IISDeploymentParameters.PublishApplicationBeforeDeployment) + { + // For published apps, prefer the content in the web.config, but update it. + yield return WebConfigHelpers.AddOrModifyAspNetCoreSection( + key: "hostingModel", + value: DeploymentParameters.HostingModel.ToString()); + + yield return WebConfigHelpers.AddOrModifyHandlerSection( + key: "modules", + value: DeploymentParameters.AncmVersion.ToString()); + + // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. + // Only do it for scenarios that rely on dotnet.exe (Core, portable, etc.). + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Portable + && DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + var executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); + if (!File.Exists(executableName)) + { + throw new Exception($"Unable to find '{executableName}'.'"); + } + yield return WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", executableName); + } + } + + foreach (var action in base.GetWebConfigActions()) + { + yield return action; + } + } + + private string GetIISExpressPath() + { + var programFiles = "Program Files"; + if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + programFiles = "Program Files (x86)"; + } + + // Get path to program files + var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", programFiles, "IIS Express", "iisexpress.exe"); + + if (!File.Exists(iisExpressPath)) + { + throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath); + } + + return iisExpressPath; + } + + public override void Dispose() + { + Dispose(gracefulShutdown: false); + } + + public override void Dispose(bool gracefulShutdown) + { + using (Logger.BeginScope("Dispose")) + { + if (gracefulShutdown) + { + GracefullyShutdownProcess(_hostProcess); + } + else + { + ShutDownIfAnyHostProcess(_hostProcess); + } + + if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) + && File.Exists(DeploymentParameters.ServerConfigLocation)) + { + // Delete the temp applicationHostConfig that we created. + Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation); + try + { + File.Delete(DeploymentParameters.ServerConfigLocation); + } + catch (Exception exception) + { + // Ignore delete failures - just write a log. + Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message); + } + } + + if (DeploymentParameters.PublishApplicationBeforeDeployment) + { + CleanPublishedOutput(); + } + + InvokeUserApplicationCleanup(); + + StopTimer(); + } + + // If by this point, the host process is still running (somehow), throw an error. + // A test failure is better than a silent hang and unknown failure later on + if (_hostProcess != null && !_hostProcess.HasExited) + { + throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown"); + } + } + + private class WindowsNativeMethods + { + [DllImport("user32.dll")] + internal static extern IntPtr GetTopWindow(IntPtr hWnd); + [DllImport("user32.dll")] + internal static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); + [DllImport("user32.dll")] + internal static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint lpdwProcessId); + [DllImport("user32.dll")] + internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + } + + private static void SendStopMessageToProcess(int pid) + { + for (var ptr = WindowsNativeMethods.GetTopWindow(IntPtr.Zero); ptr != IntPtr.Zero; ptr = WindowsNativeMethods.GetWindow(ptr, 2)) + { + uint num; + WindowsNativeMethods.GetWindowThreadProcessId(ptr, out num); + if (pid == num) + { + var hWnd = new HandleRef(null, ptr); + WindowsNativeMethods.PostMessage(hWnd, 0x12, IntPtr.Zero, IntPtr.Zero); + return; + } + } + } + + private void GracefullyShutdownProcess(Process hostProcess) + { + if (hostProcess != null && !hostProcess.HasExited) + { + // Calling hostProcess.StandardInput.WriteLine("q") with StandardInput redirected + // for the process does not work when stopping IISExpress + // Also, hostProcess.CloseMainWindow() doesn't work either. + // Instead we have to send WM_QUIT to the iisexpress process via pInvokes. + // See: https://stackoverflow.com/questions/4772092/starting-and-stopping-iis-express-programmatically + + SendStopMessageToProcess(hostProcess.Id); + if (!hostProcess.WaitForExit((int)ShutdownTimeSpan.TotalMilliseconds)) + { + throw new InvalidOperationException($"iisexpress Process {hostProcess.Id} failed to gracefully shutdown."); + } + if (hostProcess.ExitCode != 0) + { + Logger.LogWarning($"IISExpress exit code is non-zero after graceful shutdown. Exit code: {hostProcess.ExitCode}"); + } + } + else + { + throw new InvalidOperationException($"iisexpress Process {hostProcess?.Id} crashed before shutdown was triggered."); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs new file mode 100644 index 0000000000..d9067c038e --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs @@ -0,0 +1,52 @@ +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class LoggingHandler: DelegatingHandler + { + private readonly int _maxBodyLogSize = 16 * 1024; + private readonly ILogger _logger; + + public LoggingHandler(HttpMessageHandler innerHandler, ILogger logger) + : base(innerHandler) + { + _logger = logger; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + _logger.LogDebug(request.ToString()); + var response = await base.SendAsync(request, cancellationToken); + + await LogResponse(response.IsSuccessStatusCode ? LogLevel.Debug : LogLevel.Warning, response); + return response; + } + + private async Task LogResponse(LogLevel logLevel, HttpResponseMessage response) + { + _logger.Log(logLevel, response.ToString()); + if (response.Content != null) + { + await response.Content.LoadIntoBufferAsync(); + var readAsStreamAsync = await response.Content.ReadAsStreamAsync(); + var buffer = new byte[_maxBodyLogSize]; + var offset = 0; + var count = 0; + do + { + count = await readAsStreamAsync.ReadAsync(buffer, offset, buffer.Length - offset); + offset += count; + } while (count != 0 && offset != buffer.Length); + + readAsStreamAsync.Position = 0; + _logger.Log(logLevel, Encoding.ASCII.GetString(buffer, 0, offset)); + } + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj new file mode 100644 index 0000000000..bdfe8abfa9 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj @@ -0,0 +1,47 @@ + + + + netstandard2.0 + Microsoft.AspNetCore.Server.IntegrationTesting.IIS + Provides support for integration testing using IIS based servers. + $(NoWarn);CS1591 + true + aspnetcore;iis + True + + + + + + + + + + + + + + + + + + False + + + False + + + False + + + + + + + + + + + + + diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ProcessTracker.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ProcessTracker.cs new file mode 100644 index 0000000000..be2e6cc62b --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ProcessTracker.cs @@ -0,0 +1,128 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + // Uses Windows Job Objects to ensure external processes are killed if the current process is terminated non-gracefully. + internal static class ProcessTracker + { + private static readonly IntPtr _jobHandle; + + static ProcessTracker() + { + // Requires Win8 or later + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || Environment.OSVersion.Version < new Version(6, 2)) + { + return; + } + + // Job name is optional but helps with diagnostics. Job name must be unique if non-null. + _jobHandle = CreateJobObject(IntPtr.Zero, name: $"ProcessTracker{Process.GetCurrentProcess().Id}"); + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION + { + LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + } + }; + + var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + var extendedInfoPtr = Marshal.AllocHGlobal(length); + try + { + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(_jobHandle, JobObjectInfoType.ExtendedLimitInformation, + extendedInfoPtr, (uint)length)) + { + throw new Win32Exception(); + } + } + finally + { + Marshal.FreeHGlobal(extendedInfoPtr); + } + } + + public static void Add(Process process) + { + if (_jobHandle != IntPtr.Zero) + { + var success = AssignProcessToJobObject(_jobHandle, process.Handle); + if (!success && !process.HasExited) + { + throw new Win32Exception(); + } + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); + + [DllImport("kernel32.dll")] + static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, + IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + private enum JobObjectInfoType + { + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUIRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_BASIC_LIMIT_INFORMATION + { + public Int64 PerProcessUserTimeLimit; + public Int64 PerJobUserTimeLimit; + public JOBOBJECTLIMIT LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public UInt32 ActiveProcessLimit; + public Int64 Affinity; + public UInt32 PriorityClass; + public UInt32 SchedulingClass; + } + + [Flags] + private enum JOBOBJECTLIMIT : uint + { + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 + } + + [StructLayout(LayoutKind.Sequential)] + private struct IO_COUNTERS + { + public UInt64 ReadOperationCount; + public UInt64 WriteOperationCount; + public UInt64 OtherOperationCount; + public UInt64 ReadTransferCount; + public UInt64 WriteTransferCount; + public UInt64 OtherTransferCount; + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs new file mode 100644 index 0000000000..4282c3afca --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs @@ -0,0 +1,60 @@ +// 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.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class RetryHandler : DelegatingHandler + { + private static readonly int MaxRetries = 5; + private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(1); + + private readonly ILogger _logger; + + public RetryHandler(HttpMessageHandler innerHandler, ILogger logger) + : base(innerHandler) + { + _logger = logger; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + HttpResponseMessage response = null; + for (int i = 0; i < MaxRetries; i++) + { + response = null; + try + { + response = await base.SendAsync(request, cancellationToken); + } + catch (Exception ex) + { + _logger.LogWarning("Error sending request", ex); + if (i == MaxRetries - 1) + { + throw; + } + } + + // Retry only on 503 that is expected during IIS startup + if (response != null && + (response.IsSuccessStatusCode || response.StatusCode != (HttpStatusCode)503)) + { + break; + } + + _logger.LogDebug($"Retrying {i+1}th time after {RetryDelay.Seconds} sec."); + await Task.Delay(RetryDelay, cancellationToken); + } + return response; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs new file mode 100644 index 0000000000..f920622568 --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs @@ -0,0 +1,36 @@ +// 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.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public static class WebConfigHelpers + { + public static Action AddOrModifyAspNetCoreSection(string key, string value) + { + return (element, _) => { + element + .Descendants("system.webServer") + .Single() + .GetOrAdd("aspNetCore") + .SetAttributeValue(key, value); + }; + } + + public static Action AddOrModifyHandlerSection(string key, string value) + { + return (element, _) => + { + element + .Descendants("system.webServer") + .Single() + .GetOrAdd("handlers") + .GetOrAdd("add") + .SetAttributeValue(key, value); + }; + } + } +} diff --git a/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs new file mode 100644 index 0000000000..35d1b013cd --- /dev/null +++ b/src/IISIntegration/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public static class XElementExtensions + { + public static XElement RequiredElement(this XElement element, string name) + { + var existing = element.Element(name); + if (existing == null) + { + throw new InvalidOperationException($"Element with name {name} not found in {element}"); + } + + return existing; + } + + public static XElement GetOrAdd(this XElement element, string name) + { + var existing = element.Element(name); + if (existing == null) + { + existing = new XElement(name); + element.Add(existing); + } + + return existing; + } + + public static XElement GetOrAdd(this XElement element, string name, string attribute, string attributeValue) + { + var existing = element.Elements(name).FirstOrDefault(e => e.Attribute(attribute)?.Value == attributeValue); + if (existing == null) + { + existing = new XElement(name, new XAttribute(attribute, attributeValue)); + element.Add(existing); + } + + return existing; + } + } +} diff --git a/src/IISIntegration/test/AspNetCoreModuleTests/stdafx.h b/src/IISIntegration/test/AspNetCoreModuleTests/stdafx.h deleted file mode 100644 index 67bd5aa27b..0000000000 --- a/src/IISIntegration/test/AspNetCoreModuleTests/stdafx.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#include "targetver.h" - -#define WIN32_LEAN_AND_MEAN - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "stringa.h" -#include "stringu.h" -#include "dbgutil.h" -#include "ahutil.h" -#include "multisz.h" -#include "multisza.h" -#include "base64.h" -#include -#include -#include -#include -#include - -#include "..\..\src\AspNetCoreModuleV2\IISLib\hashtable.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\stringu.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\stringa.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\multisz.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\dbgutil.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\ahutil.h" -#include "..\..\src\AspNetCoreModuleV2\IISLib\hashfn.h" - -#include "..\..\src\AspNetCoreModuleV2\CommonLib\hostfxr_utility.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\environmentvariablehash.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\aspnetcoreconfig.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\application.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\utility.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\debugutil.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\requesthandler.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\resources.h" -#include "..\..\src\AspNetCoreModuleV2\CommonLib\aspnetcore_msg.h" - -#include "CppUnitTest.h" diff --git a/src/IISIntegration/test/Common.FunctionalTests/AppHostConfig/IIS.config b/src/IISIntegration/test/Common.FunctionalTests/AppHostConfig/IIS.config new file mode 100644 index 0000000000..bc24ef9639 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/AppHostConfig/IIS.config @@ -0,0 +1,740 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ + +
+
+
+
+ +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/Common.FunctionalTests/AppOfflineTests.cs b/src/IISIntegration/test/Common.FunctionalTests/AppOfflineTests.cs new file mode 100644 index 0000000000..7d8d661ef0 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/AppOfflineTests.cs @@ -0,0 +1,287 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class AppOfflineTests : IISFunctionalTestBase + { + private static readonly TimeSpan RetryDelay = TimeSpan.FromMilliseconds(100); + + private readonly PublishedSitesFixture _fixture; + + public AppOfflineTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDroppedWhileSiteIsDown_SiteReturns503(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task LockedAppOfflineDroppedWhileSiteIsDown_SiteReturns503(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + // Add app_offline without shared access + using (var stream = File.Open(Path.Combine(deploymentResult.ContentRoot, "app_offline.htm"), FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None)) + using (var writer = new StreamWriter(stream)) + { + await writer.WriteLineAsync("App if offline but you wouldn't see this message"); + await writer.FlushAsync(); + await AssertAppOffline(deploymentResult, ""); + } + + DeletePublishOutput(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess, 500, "500.0")] + [InlineData(HostingModel.OutOfProcess, 502, "502.5")] + public async Task AppOfflineDroppedWhileSiteFailedToStartInShim_AppOfflineServed(HostingModel hostingModel, int statusCode, string content) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "nonexistent")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var result = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(statusCode, (int)result.StatusCode); + Assert.Contains(content, await result.Content.ReadAsStringAsync()); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalFact(Skip = "https://github.com/aspnet/IISIntegration/issues/933")] + public async Task AppOfflineDroppedWhileSiteFailedToStartInRequestHandler_SiteStops_InProcess() + { + var deploymentResult = await DeployApp(HostingModel.InProcess); + + // Set file content to empty so it fails at runtime + File.WriteAllText(Path.Combine(deploymentResult.ContentRoot, "Microsoft.AspNetCore.Server.IIS.dll"), ""); + + var result = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(500, (int)result.StatusCode); + Assert.Contains("500.30", await result.Content.ReadAsStringAsync()); + + AddAppOffline(deploymentResult.ContentRoot); + + await deploymentResult.AssertRecycledAsync(() => AssertAppOffline(deploymentResult)); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task AppOfflineDroppedWhileSiteStarting_SiteShutsDown_InProcess() + { + // This test often hits a race between debug logging and stdout redirection closing the handle + // we are fine having this race + using (AppVerifier.Disable(DeployerSelector.ServerType, 0x300)) + { + var deploymentResult = await DeployApp(HostingModel.InProcess); + + for (int i = 0; i < 10; i++) + { + // send first request and add app_offline while app is starting + var runningTask = AssertAppOffline(deploymentResult); + + // This test tries to hit a race where we drop app_offline file while + // in process application is starting, application start takes at least 400ms + // so we back off for 100ms to allow request to reach request handler + // Test itself is racy and can result in two scenarios + // 1. ANCM detects app_offline before it starts the request - if AssertAppOffline succeeds we've hit it + // 2. Intended scenario where app starts and then shuts down + // In first case we remove app_offline and try again + await Task.Delay(RetryDelay); + + AddAppOffline(deploymentResult.ContentRoot); + + try + { + await runningTask.DefaultTimeout(); + + // if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app + // try again + RemoveAppOffline(deploymentResult.ContentRoot); + } + catch + { + deploymentResult.AssertWorkerProcessStop(); + return; + } + } + + Assert.True(false); + + } + } + + [ConditionalFact] + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess() + { + var deploymentResult = await AssertStarts(HostingModel.InProcess); + + AddAppOffline(deploymentResult.ContentRoot); + + await deploymentResult.AssertRecycledAsync(() => AssertAppOffline(deploymentResult)); + } + + [ConditionalFact] + public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_OutOfProcess() + { + var deploymentResult = await AssertStarts(HostingModel.OutOfProcess); + + // Repeat dropping file and restarting multiple times + for (int i = 0; i < 5; i++) + { + AddAppOffline(deploymentResult.ContentRoot); + await AssertAppOffline(deploymentResult); + RemoveAppOffline(deploymentResult.ContentRoot); + await AssertRunning(deploymentResult); + } + + AddAppOffline(deploymentResult.ContentRoot); + await AssertAppOffline(deploymentResult); + DeletePublishOutput(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineDropped_CanRemoveAppOfflineAfterAddingAndSiteWorks(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + AddAppOffline(deploymentResult.ContentRoot); + + await AssertAppOffline(deploymentResult); + + RemoveAppOffline(deploymentResult.ContentRoot); + + await AssertRunning(deploymentResult); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task AppOfflineAddedAndRemovedStress(HostingModel hostingModel) + { + var deploymentResult = await AssertStarts(hostingModel); + + var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => { + var statusCode = (int)response.StatusCode; + Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode); + }); + + for (int i = 0; i < 100; i++) + { + // AddAppOffline might fail if app_offline is being read by ANCM and deleted at the same time + RetryHelper.RetryOperation( + () => AddAppOffline(deploymentResult.ContentRoot), + e => Logger.LogError($"Failed to create app_offline : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: RetryDelay.Milliseconds); + RemoveAppOffline(deploymentResult.ContentRoot); + } + + try + { + await load; + } + catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException) + { + // IOException in InProcess is fine, just means process stopped + if (hostingModel != HostingModel.InProcess) + { + throw; + } + } + } + + private async Task DeployApp(HostingModel hostingModel = HostingModel.InProcess) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel: hostingModel, publish: true); + + return await DeployAsync(deploymentParameters); + } + + private void AddAppOffline(string appPath, string content = "The app is offline.") + { + File.WriteAllText(Path.Combine(appPath, "app_offline.htm"), content); + } + + private void RemoveAppOffline(string appPath) + { + RetryHelper.RetryOperation( + () => File.Delete(Path.Combine(appPath, "app_offline.htm")), + e => Logger.LogError($"Failed to remove app_offline : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: RetryDelay.Milliseconds); + } + + private async Task AssertAppOffline(IISDeploymentResult deploymentResult, string expectedResponse = "The app is offline.") + { + var response = await deploymentResult.HttpClient.RetryRequestAsync("HelloWorld", r => r.StatusCode == HttpStatusCode.ServiceUnavailable); + Assert.Equal(expectedResponse, await response.Content.ReadAsStringAsync()); + } + + private async Task AssertStarts(HostingModel hostingModel) + { + var deploymentResult = await DeployApp(hostingModel); + + await AssertRunning(deploymentResult); + + return deploymentResult; + } + + private static async Task AssertRunning(IISDeploymentResult deploymentResult) + { + var response = await deploymentResult.HttpClient.RetryRequestAsync("HelloWorld", r => r.IsSuccessStatusCode); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World", responseText); + } + + private void DeletePublishOutput(IISDeploymentResult deploymentResult) + { + foreach (var file in Directory.GetFiles(deploymentResult.ContentRoot, "*", SearchOption.AllDirectories)) + { + // Out of process module dll is allowed to be locked + var name = Path.GetFileName(file); + if (name == "aspnetcore.dll" || name == "aspnetcorev2.dll" || name == "aspnetcorev2_outofprocess.dll") + { + continue; + } + File.Delete(file); + } + } + + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/BasicAuthTests.cs b/src/IISIntegration/test/Common.FunctionalTests/BasicAuthTests.cs new file mode 100644 index 0000000000..d8607db21e --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/BasicAuthTests.cs @@ -0,0 +1,70 @@ +// 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.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class BasicAuthTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public BasicAuthTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithApplicationTypes(ApplicationType.Portable) + .WithAllAncmVersions() + .WithAllHostingModels(); + + [ConditionalTheory] + [RequiresEnvironmentVariable("ASPNETCORE_MODULE_TEST_USER")] + [RequiresIIS(IISCapability.BasicAuthentication)] + [MemberData(nameof(TestVariants))] + public async Task BasicAuthTest(TestVariant variant) + { + var username = Environment.GetEnvironmentVariable("ASPNETCORE_MODULE_TEST_USER"); + var password = Environment.GetEnvironmentVariable("ASPNETCORE_MODULE_TEST_PASSWORD"); + + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.SetAnonymousAuth(enabled: false); + deploymentParameters.SetWindowsAuth(enabled: false); + deploymentParameters.SetBasicAuth(enabled: true); + + // The default in hosting sets windows auth to true. + var deploymentResult = await DeployAsync(deploymentParameters); + var request = new HttpRequestMessage(HttpMethod.Get, "/Auth"); + var byteArray = new UTF8Encoding().GetBytes(username + ":" + password); + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); + + var response = await deploymentResult.HttpClient.SendAsync(request); + + var responseText = await response.Content.ReadAsStringAsync(); + + if (variant.HostingModel == HostingModel.InProcess) + { + Assert.StartsWith("Windows", responseText); + Assert.Contains(username, responseText); + } + else + { + // We expect out-of-proc not allowing basic auth + Assert.Equal("Windows", responseText); + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateFixture.cs b/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateFixture.cs new file mode 100644 index 0000000000..40b6f2265e --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateFixture.cs @@ -0,0 +1,104 @@ +// 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.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class ClientCertificateFixture : IDisposable + { + private X509Certificate2 _certificate; + private const string _certIssuerPrefix = "CN=IISIntegrationTest_Root"; + + public X509Certificate2 GetOrCreateCertificate() + { + if (_certificate != null) + { + return _certificate; + } + + using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) + { + store.Open(OpenFlags.ReadWrite); + var parentKey = CreateKeyMaterial(2048); + + // Create a cert name with a random guid to avoid name conflicts + var parentRequest = new CertificateRequest( + _certIssuerPrefix + Guid.NewGuid().ToString(), + parentKey, HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + parentRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: false, + pathLengthConstraint: 0, + critical: true)); + + parentRequest.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, critical: true)); + + parentRequest.CertificateExtensions.Add( + new X509SubjectKeyIdentifierExtension(parentRequest.PublicKey, false)); + + var notBefore = DateTimeOffset.Now.AddDays(-1); + var notAfter = DateTimeOffset.Now.AddYears(5); + + var parentCert = parentRequest.CreateSelfSigned(notBefore, notAfter); + + // Need to export/import the certificate to associate the private key with the cert. + var imported = parentCert; + + var export = parentCert.Export(X509ContentType.Pkcs12, ""); + imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + Array.Clear(export, 0, export.Length); + + // Add the cert to the cert store + _certificate = imported; + + store.Add(certificate: imported); + store.Close(); + return imported; + } + } + + public void Dispose() + { + if (_certificate == null) + { + return; + } + + using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) + { + store.Open(OpenFlags.ReadWrite); + store.Remove(_certificate); + + // Remove any extra certs that were left by previous tests. + for (var i = store.Certificates.Count - 1; i >= 0; i--) + { + var cert = store.Certificates[i]; + if (cert.Issuer.StartsWith(_certIssuerPrefix)) + { + store.Remove(cert); + } + } + store.Close(); + } + } + + private RSA CreateKeyMaterial(int minimumKeySize) + { + var rsa = RSA.Create(minimumKeySize); + if (rsa.KeySize < minimumKeySize) + { + throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits"); + } + + return rsa; + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateTests.cs b/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateTests.cs new file mode 100644 index 0000000000..43ccc83eff --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/ClientCertificateTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + [SkipIfNotAdmin] + public class ClientCertificateTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + private readonly ClientCertificateFixture _certFixture; + + public ClientCertificateTests(PublishedSitesFixture fixture, ClientCertificateFixture certFixture) + { + _fixture = fixture; + _certFixture = certFixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22, Tfm.Net461) + .WithAllApplicationTypes() + .WithAllAncmVersions() + .WithAllHostingModels(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public Task HttpsNoClientCert_NoClientCert(TestVariant variant) + { + return ClientCertTest(variant, sendClientCert: false); + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public Task HttpsClientCert_GetCertInformation(TestVariant variant) + { + return ClientCertTest(variant, sendClientCert: true); + } + + private async Task ClientCertTest(TestVariant variant, bool sendClientCert) + { + var port = TestPortHelper.GetNextSSLPort(); + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; + deploymentParameters.AddHttpsToServerConfig(); + + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (a, b, c, d) => true, + ClientCertificateOptions = ClientCertificateOption.Manual, + }; + + X509Certificate2 cert = null; + if (sendClientCert) + { + cert = _certFixture.GetOrCreateCertificate(); + handler.ClientCertificates.Add(cert); + } + + var deploymentResult = await DeployAsync(deploymentParameters); + + var client = deploymentResult.CreateClient(handler); + var response = await client.GetAsync("GetClientCert"); + + var responseText = await response.Content.ReadAsStringAsync(); + + try + { + if (sendClientCert) + { + Assert.Equal($"Enabled;{cert.GetCertHashString()}", responseText); + } + else + { + Assert.Equal("Disabled", responseText); + } + } + catch (Exception ex) + { + Logger.LogError($"Certificate is invalid. Issuer name: {cert.Issuer}"); + using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) + { + Logger.LogError($"List of current certificates in root store:"); + store.Open(OpenFlags.ReadWrite); + foreach (var otherCert in store.Certificates) + { + Logger.LogError(otherCert.Issuer); + } + store.Close(); + } + throw ex; + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/ClientDisconnectStress.cs b/src/IISIntegration/test/Common.FunctionalTests/ClientDisconnectStress.cs new file mode 100644 index 0000000000..9deeae3f92 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/ClientDisconnectStress.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 System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ClientDisconnectStressTests: FunctionalTestsBase + { + private readonly PublishedSitesFixture _fixture; + + public ClientDisconnectStressTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task ClientDisconnectStress(HostingModel hostingModel) + { + var site = await StartAsync(_fixture.GetBaseDeploymentParameters(hostingModel)); + var maxRequestSize = 1000; + var blockSize = 40; + var random = new Random(); + async Task RunRequests() + { + using (var connection = new TestConnection(site.HttpClient.BaseAddress.Port)) + { + await connection.Send( + "POST /ReadAndFlushEcho HTTP/1.1", + $"Content-Length: {maxRequestSize}", + "Host: localhost", + "Connection: close", + "", + ""); + + var disconnectAfter = random.Next(maxRequestSize); + var data = new byte[blockSize]; + for (int i = 0; i < disconnectAfter / blockSize; i++) + { + await connection.Stream.WriteAsync(data); + } + } + } + + List tasks = new List(); + for (int i = 0; i < 100; i++) + { + tasks.Add(Task.Run(RunRequests)); + } + + await Task.WhenAll(tasks); + + StopServer(); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/CommonStartupTests.cs b/src/IISIntegration/test/Common.FunctionalTests/CommonStartupTests.cs new file mode 100644 index 0000000000..e2bcf2a8f9 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/CommonStartupTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class CommonStartupTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public CommonStartupTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithAllApplicationTypes() + .WithAllAncmVersions() + .WithAllHostingModels(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task StartupStress(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Hello World", response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + }); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/CompressionTests.cs b/src/IISIntegration/test/Common.FunctionalTests/CompressionTests.cs new file mode 100644 index 0000000000..c2d0277c4c --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/CompressionTests.cs @@ -0,0 +1,58 @@ +// 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.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISCompressionSiteCollection.Name)] + public abstract class CompressionTests : FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + [Collection(IISTestSiteCollection.Name)] + public class InProc: CompressionTests + { + public InProc(IISTestSiteFixture fixture) : base(fixture) { } + } + + [Collection(OutOfProcessTestSiteCollection.Name)] + public class OutOfProcess: CompressionTests + { + public OutOfProcess(OutOfProcessTestSiteFixture fixture) : base(fixture) { } + } + + [Collection(OutOfProcessV1TestSiteCollection.Name)] + public class OutOfProcessV1: CompressionTests + { + public OutOfProcessV1(OutOfProcessV1TestSiteFixture fixture) : base(fixture) { } + } + + protected CompressionTests(IISTestSiteFixture fixture) : base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task PassesThroughCompression() + { + var request = new HttpRequestMessage(HttpMethod.Get, "/CompressedData"); + + request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + + var response = await _fixture.Client.SendAsync(request); + Assert.Equal("gzip", response.Content.Headers.ContentEncoding.Single()); + Assert.Equal( + new byte[] { + 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x0B, 0x63, 0x60, 0xA0, 0x3D, 0x00, 0x00, + 0xCA, 0xC6, 0x88, 0x99, 0x64, 0x00, 0x00, 0x00 }, + await response.Content.ReadAsByteArrayAsync()); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/ConfigurationChangeTests.cs b/src/IISIntegration/test/Common.FunctionalTests/ConfigurationChangeTests.cs new file mode 100644 index 0000000000..099cefb97a --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/ConfigurationChangeTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ConfigurationChangeTests : IISFunctionalTestBase + { + private static readonly TimeSpan RetryDelay = TimeSpan.FromMilliseconds(100); + private readonly PublishedSitesFixture _fixture; + + public ConfigurationChangeTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ConfigurationChangeStopsInProcess() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(HostingModel.InProcess, publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await deploymentResult.AssertStarts(); + + // Just "touching" web.config should be enough + deploymentResult.ModifyWebConfig(element => {}); + + await deploymentResult.AssertRecycledAsync(); + } + + [ConditionalTheory] + [InlineData(AncmVersion.AspNetCoreModule)] + [InlineData(AncmVersion.AspNetCoreModuleV2)] + public async Task ConfigurationChangeForcesChildProcessRestart(AncmVersion version) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + deploymentParameters.AncmVersion = version; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var processBefore = await deploymentResult.HttpClient.GetStringAsync("/ProcessId"); + + // Just "touching" web.config should be enough + deploymentResult.ModifyWebConfig(element => {}); + + // Have to retry here to allow ANCM to receive notification and react to it + // Verify that worker process gets restarted with new process id + await deploymentResult.HttpClient.RetryRequestAsync("/ProcessId", async r => await r.Content.ReadAsStringAsync() != processBefore); + } + + [ConditionalFact] + public async Task OutOfProcessToInProcessHostingModelSwitchWorks() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await deploymentResult.AssertStarts(); + + deploymentResult.ModifyWebConfig(element => element + .Descendants("system.webServer") + .Single() + .GetOrAdd("aspNetCore") + .SetAttributeValue("hostingModel", "inprocess")); + + // Have to retry here to allow ANCM to receive notification and react to it + // Verify that inprocess application was created and tried to start + await deploymentResult.HttpClient.RetryRequestAsync("/HelloWorld", r => r.StatusCode == HttpStatusCode.InternalServerError); + + StopServer(); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not find the assembly 'aspnetcorev2_inprocess.dll'", Logger); + } + + [ConditionalTheory] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task ConfigurationTouchedStress(HostingModel hostingModel) + { + var deploymentResult = await DeployAsync(_fixture.GetBaseDeploymentParameters(hostingModel, publish: true)); + + await deploymentResult.AssertStarts(); + var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => { + var statusCode = (int)response.StatusCode; + Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode); + }); + + for (int i = 0; i < 100; i++) + { + // ModifyWebConfig might fail if web.config is being read by IIS + RetryHelper.RetryOperation( + () => deploymentResult.ModifyWebConfig(element => {}), + e => Logger.LogError($"Failed to touch web.config : {e.Message}"), + retryCount: 3, + retryDelayMilliseconds: RetryDelay.Milliseconds); + } + + try + { + await load; + } + catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException) + { + // IOException in InProcess is fine, just means process stopped + if (hostingModel != HostingModel.InProcess) + { + throw; + } + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs new file mode 100644 index 0000000000..6e2d1bc752 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ClientDisconnectTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ClientDisconnectTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public ClientDisconnectTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ServerWorksAfterClientDisconnect() + { + using (var connection = _fixture.CreateTestConnection()) + { + var message = "Hello"; + await connection.Send( + "POST /ReadAndWriteSynchronously HTTP/1.1", + $"Content-Length: {100000}", + "Host: localhost", + "Connection: close", + "", + ""); + + await connection.Send(message); + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + } + + var response = await _fixture.Client.GetAsync("HelloWorld"); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World", responseText); + } + + [ConditionalFact] + public async Task RequestAbortedTokenFires() + { + using (var connection = _fixture.CreateTestConnection()) + { + await connection.Send( + "GET /WaitForAbort HTTP/1.1", + "Host: localhost", + "Connection: close", + "", + ""); + + await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "1"); + } + + await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "0"); + } + + [ConditionalFact] + public async Task ClientDisconnectCallbackStress() + { + // Fixture initialization fails if inside of the Task.Run, so send an + // initial request to initialize the fixture. + var response = await _fixture.Client.GetAsync("HelloWorld"); + var numTotalRequests = 0; + for (var j = 0; j < 20; j++) + { + // Windows has a max connection limit of 10 for the IIS server, + // so setting limit fairly low. + const int numRequests = 5; + async Task RunRequests() + { + using (var connection = _fixture.CreateTestConnection()) + { + await connection.Send( + "GET /WaitForAbort HTTP/1.1", + "Host: localhost", + "Connection: close", + "", + ""); + await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() != "0"); + Interlocked.Increment(ref numTotalRequests); + } + } + + List tasks = new List(); + for (int i = 0; i < numRequests; i++) + { + tasks.Add(Task.Run(RunRequests)); + } + + await Task.WhenAll(tasks); + + await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "0"); + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/CompressionTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/CompressionTests.cs new file mode 100644 index 0000000000..ce1c84e609 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/CompressionTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISCompressionSiteCollection.Name)] + public class CompressionModuleTests : FixtureLoggedTest + { + private readonly IISCompressionSiteFixture _fixture; + + public CompressionModuleTests(IISCompressionSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.DynamicCompression)] + [InlineData(true)] + [InlineData(false)] + public async Task BufferingDisabled(bool compression) + { + using (var connection = _fixture.CreateTestConnection()) + { + var requestLength = 0; + var messages = new List(); + for (var i = 1; i < 100; i++) + { + var message = i + Environment.NewLine; + requestLength += message.Length; + messages.Add(message); + } + + await connection.Send( + "POST /ReadAndWriteEchoLinesNoBuffering HTTP/1.1", + $"Content-Length: {requestLength}", + "Accept-Encoding: " + (compression ? "gzip": "identity"), + "Response-Content-Type: text/event-stream", + "Host: localhost", + "Connection: close", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + await connection.ReceiveHeaders(); + + foreach (var message in messages) + { + await connection.Send(message); + await connection.ReceiveChunk(message); + } + + await connection.Send("\r\n"); + await connection.ReceiveChunk(""); + await connection.WaitForConnectionClose(); + } + } + + [ConditionalFact] + [RequiresIIS(IISCapability.DynamicCompression)] + public async Task DynamicResponsesAreCompressed() + { + var handler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip + }; + var client = new HttpClient(handler) + { + BaseAddress = _fixture.Client.BaseAddress, + }; + client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("identity", 0)); + client.DefaultRequestHeaders.Add("Response-Content-Type", "text/event-stream"); + var messages = "Message1\r\nMessage2\r\n"; + + // Send messages with terminator + var response = await client.PostAsync("ReadAndWriteEchoLines", new StringContent(messages + "\r\n")); + Assert.Equal(messages, await response.Content.ReadAsStringAsync()); + Assert.True(response.Content.Headers.TryGetValues("Content-Type", out var contentTypes)); + Assert.Single(contentTypes, "text/event-stream"); + // Not the cleanest check but I wasn't able to figure out other way to check + // that response was compressed + Assert.Contains("gzip", response.Content.GetType().FullName, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs new file mode 100644 index 0000000000..ae8fde39ed --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class EnvironmentVariableTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public EnvironmentVariableTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task GetUniqueEnvironmentVariable() + { + Assert.Equal("foobar", await _fixture.Client.GetStringAsync("/CheckEnvironmentVariable")); + } + + [ConditionalFact] + public async Task GetLongEnvironmentVariable() + { + Assert.Equal( + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" + + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" + + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" + + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" + + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" + + "AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative", + await _fixture.Client.GetStringAsync("/CheckEnvironmentLongValueVariable")); + } + + [ConditionalFact] + public async Task GetExistingEnvironmentVariable() + { + Assert.Contains(";foobarbaz", await _fixture.Client.GetStringAsync("/CheckAppendedEnvironmentVariable")); + } + + [ConditionalFact] + public async Task AuthHeaderEnvironmentVariableRemoved() + { + Assert.DoesNotContain("shouldberemoved", await _fixture.Client.GetStringAsync("/CheckRemoveAuthEnvironmentVariable")); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs new file mode 100644 index 0000000000..bf45949491 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ErrorPagesTests.cs @@ -0,0 +1,131 @@ +// 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.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ErrorPagesTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public ErrorPagesTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task IncludesAdditionalErrorPageTextInProcessHandlerLoadFailure_CorrectString() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var response = await DeployAppWithStartupFailure(deploymentParameters); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + var responseString = await response.Content.ReadAsStringAsync(); + Assert.Contains("HTTP Error 500.0 - ANCM In-Process Handler Load Failure", responseString); + VerifyNoExtraTrailingBytes(responseString); + + await AssertLink(response); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task IncludesAdditionalErrorPageTextOutOfProcessStartupFailure_CorrectString() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + var response = await DeployAppWithStartupFailure(deploymentParameters); + + Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode); + + StopServer(); + + var responseString = await response.Content.ReadAsStringAsync(); + Assert.Contains("HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure", responseString); + VerifyNoExtraTrailingBytes(responseString); + + await AssertLink(response); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task IncludesAdditionalErrorPageTextOutOfProcessHandlerLoadFailure_CorrectString() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + deploymentParameters.HandlerSettings["handlerVersion"] = "88.93"; + deploymentParameters.EnvironmentVariables["ANCM_ADDITIONAL_ERROR_PAGE_LINK"] = "http://example"; + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + var responseString = await response.Content.ReadAsStringAsync(); + Assert.Contains("HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure", responseString); + VerifyNoExtraTrailingBytes(responseString); + + await AssertLink(response); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewHandler] + public async Task IncludesAdditionalErrorPageTextInProcessStartupFailure_CorrectString() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} EarlyReturn"); + deploymentParameters.EnvironmentVariables["ANCM_ADDITIONAL_ERROR_PAGE_LINK"] = "http://example"; + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + var responseString = await response.Content.ReadAsStringAsync(); + Assert.Contains("HTTP Error 500.30 - ANCM In-Process Start Failure", responseString); + VerifyNoExtraTrailingBytes(responseString); + + await AssertLink(response); + } + + private static void VerifyNoExtraTrailingBytes(string responseString) + { + if (!DeployerSelector.IsBackwardsCompatiblityTest) + { + Assert.EndsWith("\r\n", responseString); + } + } + + private static async Task AssertLink(HttpResponseMessage response) + { + Assert.Contains(" http://example and ", await response.Content.ReadAsStringAsync()); + } + + private async Task DeployAppWithStartupFailure(IISDeploymentParameters deploymentParameters) + { + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "doesnot")); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", "start")); + + deploymentParameters.EnvironmentVariables["ANCM_ADDITIONAL_ERROR_PAGE_LINK"] = "http://example"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + return await deploymentResult.HttpClient.GetAsync("HelloWorld"); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EventLogTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EventLogTests.cs new file mode 100644 index 0000000000..1df7f3c077 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/EventLogTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class EventLogTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public EventLogTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task CheckStartupEventLogMessage() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + await deploymentResult.AssertStarts(); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' started the coreclr in-process successfully."); + } + + [ConditionalFact] + public async Task CheckShutdownEventLogMessage() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var deploymentResult = await DeployAsync(deploymentParameters); + await deploymentResult.AssertStarts(); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Application '.+' has shutdown."); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FeatureCollectionTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FeatureCollectionTests.cs new file mode 100644 index 0000000000..e31dc3dbaa --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FeatureCollectionTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class FeatureCollectionTest + { + private readonly IISTestSiteFixture _fixture; + + public FeatureCollectionTest(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData("FeatureCollectionSetRequestFeatures")] + [InlineData("FeatureCollectionSetResponseFeatures")] + [InlineData("FeatureCollectionSetConnectionFeatures")] + public async Task FeatureCollectionTest_SetHttpContextFeatures(string path) + { + Assert.Equal("Success", await _fixture.Client.GetStringAsync(path + "/path" + "?query")); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FixtureLoggedTest.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FixtureLoggedTest.cs new file mode 100644 index 0000000000..705af2b213 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FixtureLoggedTest.cs @@ -0,0 +1,31 @@ +// 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.Reflection; +using Microsoft.Extensions.Logging.Testing; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class FixtureLoggedTest: LoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public FixtureLoggedTest(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(methodInfo, testMethodArguments, testOutputHelper); + _fixture.Attach(this); + } + + public override void Dispose() + { + _fixture.Detach(this); + base.Dispose(); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FrebTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FrebTests.cs new file mode 100644 index 0000000000..12e2f6ae53 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/FrebTests.cs @@ -0,0 +1,181 @@ +// 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.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class FrebTests : LogFileTestBase + { + private readonly PublishedSitesFixture _fixture; + public FrebTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static IList FrebChecks() + { + var list = new List(); + list.Add(new FrebLogItem("ANCM_INPROC_EXECUTE_REQUEST_START")); + list.Add(new FrebLogItem("ANCM_INPROC_EXECUTE_REQUEST_COMPLETION", "1")); + list.Add(new FrebLogItem("ANCM_INPROC_ASYNC_COMPLETION_START")); + list.Add(new FrebLogItem("ANCM_INPROC_ASYNC_COMPLETION_COMPLETION", "0")); + list.Add(new FrebLogItem("ANCM_INPROC_MANAGED_REQUEST_COMPLETION")); + return list; + } + + [ConditionalFact] + [RequiresIIS(IISCapability.FailedRequestTracingModule)] + public async Task CheckCommonFrebEvents() + { + var result = await SetupFrebApp(); + + await result.HttpClient.GetAsync("HelloWorld"); + + StopServer(); + + AssertFrebLogs(result, FrebChecks()); + } + + [ConditionalFact] + [RequiresNewShim] + [RequiresIIS(IISCapability.FailedRequestTracingModule)] + public async Task FrebIncludesHResultFailures() + { + var parameters = _fixture.GetBaseDeploymentParameters(publish: true); + parameters.TransformArguments((args, _) => string.Empty); + var result = await SetupFrebApp(parameters); + + await result.HttpClient.GetAsync("HelloWorld"); + + StopServer(); + + AssertFrebLogs(result, new FrebLogItem("ANCM_HRESULT_FAILED"), new FrebLogItem("ANCM_EXCEPTION_CAUGHT")); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.FailedRequestTracingModule)] + public async Task CheckFailedRequestEvents() + { + var result = await SetupFrebApp(); + + await result.HttpClient.GetAsync("Throw"); + + StopServer(); + + AssertFrebLogs(result, new FrebLogItem("ANCM_INPROC_ASYNC_COMPLETION_COMPLETION", "2")); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.FailedRequestTracingModule)] + public async Task CheckFrebDisconnect() + { + var result = await SetupFrebApp(); + + using (var connection = new TestConnection(result.HttpClient.BaseAddress.Port)) + { + await connection.Send( + "GET /WaitForAbort HTTP/1.1", + "Host: localhost", + "Connection: close", + "", + ""); + await result.HttpClient.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "1"); + } + + StopServer(); + + // The order of freb logs is based on when the requests are complete. + // This is non-deterministic here, so we need to check both freb files for a request that was disconnected. + AssertFrebLogs(result, new FrebLogItem("ANCM_INPROC_REQUEST_DISCONNECT"), new FrebLogItem("ANCM_INPROC_MANAGED_REQUEST_COMPLETION")); + } + + private async Task SetupFrebApp(IISDeploymentParameters parameters = null) + { + parameters = parameters ?? _fixture.GetBaseDeploymentParameters(publish: true); + parameters.EnableFreb("Verbose", _logFolderPath); + + Directory.CreateDirectory(_logFolderPath); + var result = await DeployAsync(parameters); + return result; + } + + private void AssertFrebLogs(IISDeploymentResult result, params FrebLogItem[] expectedFrebEvents) + { + AssertFrebLogs(result, (IEnumerable)expectedFrebEvents); + } + + private void AssertFrebLogs(IISDeploymentResult result, IEnumerable expectedFrebEvents) + { + var frebEvent = GetFrebLogItems(result); + foreach (var expectedEvent in expectedFrebEvents) + { + Assert.Contains(expectedEvent, frebEvent); + } + } + + private IEnumerable GetFrebLogItems(IISDeploymentResult result) + { + var folderPath = Helpers.GetFrebFolder(_logFolderPath, result); + var xmlFiles = Directory.GetFiles(folderPath).Where(f => f.EndsWith("xml")); + var frebEvents = new List(); + + foreach (var xmlFile in xmlFiles) + { + var xDocument = XDocument.Load(xmlFile).Root; + var nameSpace = (XNamespace)"http://schemas.microsoft.com/win/2004/08/events/event"; + var eventElements = xDocument.Descendants(nameSpace + "Event"); + foreach (var eventElement in eventElements) + { + var eventElementWithOpCode = eventElement.Descendants(nameSpace + "RenderingInfo").Single().Descendants(nameSpace + "Opcode").Single(); + var requestStatus = eventElement.Element(nameSpace + "EventData").Descendants().Where(el => el.Attribute("Name").Value == "requestStatus").SingleOrDefault(); + frebEvents.Add(new FrebLogItem(eventElementWithOpCode.Value, requestStatus?.Value)); + } + } + + return frebEvents; + } + + public class FrebLogItem + { + private string _opCode; + private string _requestStatus; + + public FrebLogItem(string opCode) + { + _opCode = opCode; + } + + public FrebLogItem(string opCode, string requestStatus) + { + _opCode = opCode; + _requestStatus = requestStatus; + } + + public override bool Equals(object obj) + { + var item = obj as FrebLogItem; + return item != null && + _opCode == item._opCode && + _requestStatus == item._requestStatus; + } + + public override int GetHashCode() + { + return HashCode.Combine(_opCode, _requestStatus); + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HelloWorldTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HelloWorldTests.cs new file mode 100644 index 0000000000..1b2ad70600 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HelloWorldTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class HelloWorldInProcessTests + { + private readonly IISTestSiteFixture _fixture; + + public HelloWorldInProcessTests(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task HelloWorld_InProcess() + { + Assert.Equal("Hello World", await _fixture.Client.GetStringAsync("/HelloWorld")); + + Assert.Equal("/Path??", await _fixture.Client.GetStringAsync("/HelloWorld/Path%3F%3F?query")); + + Assert.Equal("?query", await _fixture.Client.GetStringAsync("/HelloWorld/Query%3F%3F?query")); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs new file mode 100644 index 0000000000..061b828a6c --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/HostingEnvironmentTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class HostingEnvironmentTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public HostingEnvironmentTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + [RequiresIIS(IISCapability.ShutdownToken)] + public async Task HostingEnvironmentIsCorrect() + { + Assert.Equal( + $"ContentRootPath {_fixture.DeploymentResult.ContentRoot}" + Environment.NewLine + + $"WebRootPath {_fixture.DeploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + + $"CurrentDirectory {Path.GetDirectoryName(_fixture.DeploymentResult.HostProcess.MainModule.FileName)}", + await _fixture.Client.GetStringAsync("/HostingEnvironment")); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/InvalidReadWriteOperationTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/InvalidReadWriteOperationTests.cs new file mode 100644 index 0000000000..95c05308bd --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/InvalidReadWriteOperationTests.cs @@ -0,0 +1,84 @@ +// 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.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class InvalidReadWriteOperationTests + { + private readonly IISTestSiteFixture _fixture; + + public InvalidReadWriteOperationTests(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task TestReadOffsetWorks() + { + var result = await _fixture.Client.PostAsync($"/TestReadOffsetWorks", new StringContent("Hello World")); + Assert.Equal("Hello World", await result.Content.ReadAsStringAsync()); + } + + [ConditionalTheory] + [InlineData("/InvalidOffsetSmall")] + [InlineData("/InvalidOffsetLarge")] + [InlineData("/InvalidCountSmall")] + [InlineData("/InvalidCountLarge")] + [InlineData("/InvalidCountWithOffset")] + public async Task TestInvalidReadOperations(string operation) + { + var result = await _fixture.Client.GetStringAsync($"/TestInvalidReadOperations{operation}"); + Assert.Equal("Success", result); + } + + [ConditionalTheory] + [InlineData("/NullBuffer")] + [InlineData("/InvalidCountZeroRead")] + public async Task TestValidReadOperations(string operation) + { + var result = await _fixture.Client.GetStringAsync($"/TestValidReadOperations{operation}"); + Assert.Equal("Success", result); + } + + [ConditionalTheory] + [InlineData("/NullBufferPost")] + [InlineData("/InvalidCountZeroReadPost")] + public async Task TestValidReadOperationsPost(string operation) + { + var result = await _fixture.Client.PostAsync($"/TestValidReadOperations{operation}", new StringContent("hello")); + Assert.Equal("Success", await result.Content.ReadAsStringAsync()); + } + + [ConditionalTheory] + [InlineData("/InvalidOffsetSmall")] + [InlineData("/InvalidOffsetLarge")] + [InlineData("/InvalidCountSmall")] + [InlineData("/InvalidCountLarge")] + [InlineData("/InvalidCountWithOffset")] + public async Task TestInvalidWriteOperations(string operation) + { + var result = await _fixture.Client.GetStringAsync($"/TestInvalidWriteOperations{operation}"); + Assert.Equal("Success", result); + } + + [ConditionalFact] + public async Task TestValidWriteOperations() + { + var result = await _fixture.Client.GetStringAsync($"/TestValidWriteOperations/NullBuffer"); + Assert.Equal("Success", result); + } + + [ConditionalFact] + public async Task TestValidWriteOperationsPost() + { + var result = await _fixture.Client.PostAsync($"/TestValidWriteOperations/NullBufferPost", new StringContent("hello")); + Assert.Equal("Success", await result.Content.ReadAsStringAsync()); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LargeResponseBodyTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LargeResponseBodyTests.cs new file mode 100644 index 0000000000..40db2cfdb8 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LargeResponseBodyTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class LargeResponseBodyTests + { + private readonly IISTestSiteFixture _fixture; + + public LargeResponseBodyTests(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData(65000)] + [InlineData(1000000)] + [InlineData(10000000)] + [InlineData(100000000)] + public async Task LargeResponseBodyTest_CheckAllResponseBodyBytesWritten(int query) + { + Assert.Equal(new string('a', query), await _fixture.Client.GetStringAsync($"/LargeResponseBody?length={query}")); + } + + [ConditionalFact] + public async Task LargeResponseBodyFromFile_CheckAllResponseBodyBytesWritten() + { + Assert.Equal(200000000, (await _fixture.Client.GetStringAsync($"/LargeResponseFile")).Length); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LogPipeTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LogPipeTests.cs new file mode 100644 index 0000000000..4d34c3154f --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/LogPipeTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class LogPipeTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public LogPipeTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData("ConsoleErrorWrite")] + [InlineData("ConsoleWrite")] + public async Task CheckStdoutLoggingToPipe_DoesNotCrashProcess(string path) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.AssertStarts(deploymentResult, path); + + StopServer(); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + Assert.Contains(TestSink.Writes, context => context.Message.Contains("TEST MESSAGE")); + } + } + + [ConditionalTheory] + [InlineData("ConsoleErrorWriteStartServer")] + [InlineData("ConsoleWriteStartServer")] + public async Task CheckStdoutLoggingToPipeWithFirstWrite(string path) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + var firstWriteString = "TEST MESSAGE"; + + deploymentParameters.TransformArguments((a, _) => $"{a} {path}"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.AssertStarts(deploymentResult); + + StopServer(); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + // We can't read stdout logs from IIS as they aren't redirected. + Assert.Contains(TestSink.Writes, context => context.Message.Contains(firstWriteString)); + } + } + + [ConditionalFact] + public async Task CheckUnicodePipe() + { + var path = "CheckConsoleFunctions"; + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} {path}"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync(path); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessThreadExitStdOut(deploymentResult, "12", "(.*)彡⾔(.*)")); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseHeaderTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseHeaderTests.cs new file mode 100644 index 0000000000..fec5c227ec --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseHeaderTests.cs @@ -0,0 +1,85 @@ +// 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.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ResponseHeaders + { + private readonly IISTestSiteFixture _fixture; + + public ResponseHeaders(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task AddResponseHeaders_HeaderValuesAreSetCorrectly() + { + var response = await _fixture.Client.GetAsync("ResponseHeaders"); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Request Complete", responseText); + + Assert.True(response.Headers.TryGetValues("UnknownHeader", out var headerValues)); + Assert.Equal("test123=foo", headerValues.First()); + + Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentType, out headerValues)); + Assert.Equal("text/plain", headerValues.First()); + + Assert.True(response.Headers.TryGetValues("MultiHeader", out headerValues)); + Assert.Equal(2, headerValues.Count()); + Assert.Equal("1", headerValues.First()); + Assert.Equal("2", headerValues.Last()); + } + + [ConditionalFact] + public async Task ErrorCodeIsSetForExceptionDuringRequest() + { + var response = await _fixture.Client.GetAsync("Throw"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal("Internal Server Error", response.ReasonPhrase); + } + + [ConditionalTheory] + [InlineData(200, "custom", "custom", null)] + [InlineData(200, "custom", "custom", "Custom body")] + [InlineData(200, "custom", "custom", "")] + + + [InlineData(500, "", "Internal Server Error", null)] + [InlineData(500, "", "Internal Server Error", "Custom body")] + [InlineData(500, "", "Internal Server Error", "")] + + [InlineData(400, "custom", "custom", null)] + [InlineData(400, "", "Bad Request", "Custom body")] + [InlineData(400, "", "Bad Request", "")] + + [InlineData(999, "", "", null)] + [InlineData(999, "", "", "Custom body")] + [InlineData(999, "", "", "")] + public async Task CustomErrorCodeWorks(int code, string reason, string expectedReason, string body) + { + var response = await _fixture.Client.GetAsync($"SetCustomErorCode?code={code}&reason={reason}&writeBody={body != null}&body={body}"); + Assert.Equal((HttpStatusCode)code, response.StatusCode); + Assert.Equal(expectedReason, response.ReasonPhrase); + + // ReadAsStringAsync returns empty string for empty results + Assert.Equal(body ?? string.Empty, await response.Content.ReadAsStringAsync()); + } + + [ConditionalFact] + public async Task ServerHeaderIsOverriden() + { + var response = await _fixture.Client.GetAsync("OverrideServer"); + Assert.Equal("MyServer/7.8", response.Headers.Server.Single().Product.ToString()); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs new file mode 100644 index 0000000000..c39e155e77 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ResponseInvalidOrderingTest + { + private readonly IISTestSiteFixture _fixture; + + public ResponseInvalidOrderingTest(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData("SetStatusCodeAfterWrite")] + [InlineData("SetHeaderAfterWrite")] + public async Task ResponseInvalidOrderingTests_ExpectFailure(string path) + { + Assert.Equal($"Started_{path}Threw_Finished", await _fixture.Client.GetStringAsync("/ResponseInvalidOrdering/" + path)); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs new file mode 100644 index 0000000000..d18c4c7f09 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/ServerVariablesTest.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ServerVariablesTest + { + private readonly IISTestSiteFixture _fixture; + + public ServerVariablesTest(IISTestSiteFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ProvidesAccessToServerVariables() + { + var port = _fixture.Client.BaseAddress.Port; + Assert.Equal("SERVER_PORT: " + port, await _fixture.Client.GetStringAsync("/ServerVariable?q=SERVER_PORT")); + Assert.Equal("QUERY_STRING: q=QUERY_STRING", await _fixture.Client.GetStringAsync("/ServerVariable?q=QUERY_STRING")); + } + + [ConditionalFact] + public async Task ReturnsNullForUndefinedServerVariable() + { + Assert.Equal("THIS_VAR_IS_UNDEFINED: (null)", await _fixture.Client.GetStringAsync("/ServerVariable?q=THIS_VAR_IS_UNDEFINED")); + } + + [ConditionalFact] + public async Task CanSetAndReadVariable() + { + Assert.Equal("ROUNDTRIP: 1", await _fixture.Client.GetStringAsync("/ServerVariable?v=1&q=ROUNDTRIP")); + } + + [ConditionalFact] + public async Task BasePathIsNotPrefixedBySlashSlashQuestionMark() + { + Assert.DoesNotContain(@"\\?\", await _fixture.Client.GetStringAsync("/BasePath")); + } + + [ConditionalFact] + public async Task GetServerVariableDoesNotCrash() + { + await Helpers.StressLoad(_fixture.Client, "/GetServerVariableStress", response => { + var text = response.Content.ReadAsStringAsync().Result; + Assert.StartsWith("Response Begin", text); + Assert.EndsWith("Response End", text); + }); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs new file mode 100644 index 0000000000..cadfed9781 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class StartupExceptionTests : LogFileTestBase + { + private readonly PublishedSitesFixture _fixture; + + public StartupExceptionTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData("CheckLargeStdErrWrites")] + [InlineData("CheckLargeStdOutWrites")] + [InlineData("CheckOversizedStdErrWrites")] + [InlineData("CheckOversizedStdOutWrites")] + public async Task CheckStdoutWithLargeWrites_TestSink(string mode) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} {mode}"); + var deploymentResult = await DeployAsync(deploymentParameters); + + await AssertFailsToStart(deploymentResult); + var expectedString = new string('a', 30000); + Assert.Contains(TestSink.Writes, context => context.Message.Contains(expectedString)); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessThreadExitStdOut(deploymentResult, "12", expectedString)); + } + + [ConditionalTheory] + [InlineData("CheckLargeStdErrWrites")] + [InlineData("CheckLargeStdOutWrites")] + [InlineData("CheckOversizedStdErrWrites")] + [InlineData("CheckOversizedStdOutWrites")] + public async Task CheckStdoutWithLargeWrites_LogFile(string mode) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} {mode}"); + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await AssertFailsToStart(deploymentResult); + + var contents = GetLogFileContent(deploymentResult); + var expectedString = new string('a', 30000); + + Assert.Contains(expectedString, contents); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessThreadExitStdOut(deploymentResult, "12", expectedString)); + } + + [ConditionalFact] + public async Task CheckValidConsoleFunctions() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} CheckConsoleFunctions"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await AssertFailsToStart(deploymentResult); + + Assert.Contains(TestSink.Writes, context => context.Message.Contains("Is Console redirection: True")); + } + + private async Task AssertFailsToStart(IISDeploymentResult deploymentResult) + { + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + } + + [ConditionalFact] + public async Task Gets500_30_ErrorPage() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} EarlyReturn"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.False(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Contains("500.30 - ANCM In-Process Start Failure", responseText); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs new file mode 100644 index 0000000000..3bcc134cb2 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -0,0 +1,479 @@ +// 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.Linq; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class StartupTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public StartupTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + private readonly string _dotnetLocation = DotNetCommands.GetDotNetExecutable(RuntimeArchitecture.x64); + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task ExpandEnvironmentVariableInWebConfig() + { + // Point to dotnet installed in user profile. + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "%DotnetPath%")); + await StartAsync(deploymentParameters); + } + + [ConditionalTheory] + [InlineData("bogus", "", @"Executable was not found at '.*?\\bogus.exe")] + [InlineData("c:\\random files\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\dotnet.exe'")] + [InlineData(".\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\.\\dotnet.exe'")] + [InlineData("dotnet.exe", "", @"Application arguments are empty.")] + [InlineData("dotnet.zip", "", @"Process path 'dotnet.zip' doesn't have '.exe' extension.")] + public async Task InvalidProcessPath_ExpectServerError(string path, string arguments, string subError) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", arguments)); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}"); + + Assert.Contains("HTTP Error 500.0 - ANCM In-Process Handler Load Failure", await response.Content.ReadAsStringAsync()); + } + + [ConditionalFact] + public async Task StartsWithDotnetLocationWithoutExe() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + + await StartAsync(deploymentParameters); + } + + [ConditionalFact] + public async Task StartsWithDotnetLocationUppercase() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")).ToUpperInvariant(); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + + await StartAsync(deploymentParameters); + } + + [ConditionalTheory] + [InlineData("dotnet")] + [InlineData("dotnet.EXE")] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task StartsWithDotnetOnThePath(string path) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); + + var deploymentResult = await DeployAsync(deploymentParameters); + await deploymentResult.AssertStarts(); + + StopServer(); + // Verify that in this scenario where.exe was invoked only once by shim and request handler uses cached value + Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains("Invoking where.exe to find dotnet.exe"))); + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithAllApplicationTypes() + .WithAncmV2InProcess(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task HelloWorld(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + await StartAsync(deploymentParameters); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + public async Task StartsWithPortableAndBootstraperExe() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + // We need the right dotnet on the path in IIS + deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(DotNetCommands.GetDotNetExecutable(deploymentParameters.RuntimeArchitecture)); + + // rest publisher as it doesn't support additional parameters + deploymentParameters.ApplicationPublisher = null; + // ReferenceTestTasks is workaround for https://github.com/dotnet/sdk/issues/2482 + deploymentParameters.AdditionalPublishParameters = "-p:RuntimeIdentifier=win7-x64 -p:UseAppHost=true -p:SelfContained=false -p:ReferenceTestTasks=false"; + var deploymentResult = await DeployAsync(deploymentParameters); + + Assert.True(File.Exists(Path.Combine(deploymentResult.ContentRoot, "InProcessWebSite.exe"))); + Assert.False(File.Exists(Path.Combine(deploymentResult.ContentRoot, "hostfxr.dll"))); + Assert.Contains("InProcessWebSite.exe", Helpers.ReadAllTextFromFile(Path.Combine(deploymentResult.ContentRoot, "web.config"), Logger)); + + await deploymentResult.AssertStarts(); + } + + [ConditionalFact] + public async Task DetectsOverriddenServer() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} OverriddenServer"); + + var deploymentResult = await DeployAsync(deploymentParameters); + var response = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"), + EventLogHelpers.InProcessThreadException(deploymentResult, ".*?Application is running inside IIS process but is not configured to use IIS server")); + } + + [ConditionalFact] + public async Task LogsStartupExceptionExitError() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} Throw"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"), + EventLogHelpers.InProcessThreadException(deploymentResult, ", exception code = '0xe0434352'")); + } + + [ConditionalFact] + public async Task LogsUnexpectedThreadExitError() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} EarlyReturn"); + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "CLR worker thread exited prematurely"), + EventLogHelpers.InProcessThreadExit(deploymentResult, "12")); + } + + [ConditionalFact] + public async Task RemoveHostfxrFromApp_InProcessHostfxrInvalid() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.ApplicationType = ApplicationType.Standalone; + var deploymentResult = await DeployAsync(deploymentParameters); + + File.Copy( + Path.Combine(deploymentResult.ContentRoot, "aspnetcorev2_inprocess.dll"), + Path.Combine(deploymentResult.ContentRoot, "hostfxr.dll"), + true); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessHostfxrInvalid(deploymentResult), Logger); + } + + [ConditionalFact] + public async Task TargedDifferenceSharedFramework_FailedToFindNativeDependencies() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyFrameworkVersionInRuntimeConfig(deploymentResult); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessFailedToFindNativeDependencies(deploymentResult), Logger); + } + + [ConditionalFact] + public async Task RemoveInProcessReference_FailedToFindRequestHandler() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.ApplicationType = ApplicationType.Standalone; + var deploymentResult = await DeployAsync(deploymentParameters); + + File.Delete(Path.Combine(deploymentResult.ContentRoot, "aspnetcorev2_inprocess.dll")); + + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult); + + if (DeployerSelector.IsForwardsCompatibilityTest) + { + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessFailedToFindNativeDependencies(deploymentResult), Logger); + } + else + { + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessFailedToFindRequestHandler(deploymentResult), Logger); + } + } + + [ConditionalFact] + public async Task StartupTimeoutIsApplied() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} Hang"); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("startupTimeLimit", "1")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.InProcessFailedToStart(deploymentResult, "Managed server didn't initialize after 1000 ms.") + ); + } + + [ConditionalFact] + public async Task ShutdownTimeoutIsApplied() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} HangOnStop"); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("shutdownTimeLimit", "1")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + Assert.Equal("Hello World", await deploymentResult.HttpClient.GetStringAsync("/HelloWorld")); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.InProcessStarted(deploymentResult), + EventLogHelpers.InProcessFailedToStop(deploymentResult, "")); + } + + [ConditionalFact] + public async Task CheckInvalidHostingModelParameter() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("hostingModel", "bogus")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.ConfigurationLoadError(deploymentResult, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file.") + ); + } + + private static Dictionary)> InvalidConfigTransformations = InitInvalidConfigTransformations(); + public static IEnumerable InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(InvalidConfigTransformationsScenarios))] + public async Task ReportsWebConfigAuthoringErrors(string scenario) + { + var (expectedError, action) = InvalidConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + iisDeploymentParameters.WebConfigActionList.Add((element, _) => action(element)); + var deploymentResult = await DeployAsync(iisDeploymentParameters); + var result = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode); + + // Config load errors might not allow us to initialize log file + deploymentResult.AllowNoLogs(); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvents(deploymentResult, + EventLogHelpers.ConfigurationLoadError(deploymentResult, expectedError) + ); + } + + public static Dictionary)> InitInvalidConfigTransformations() + { + var dictionary = new Dictionary)>(); + dictionary.Add("Empty process path", + ( + "Attribute 'processPath' is required.", + element => element.Descendants("aspNetCore").Single().SetAttributeValue("processPath", "") + )); + dictionary.Add("Unknown hostingModel", + ( + "Unknown hosting model 'asdf'.", + element => element.Descendants("aspNetCore").Single().SetAttributeValue("hostingModel", "asdf") + )); + dictionary.Add("environmentVariables with add", + ( + "Unable to get required configuration section 'system.webServer/aspNetCore'. Possible reason is web.config authoring error.", + element => element.Descendants("aspNetCore").Single().GetOrAdd("environmentVariables").GetOrAdd("add") + )); + return dictionary; + } + + private static Dictionary> PortableConfigTransformations = InitPortableWebConfigTransformations(); + public static IEnumerable PortableConfigTransformationsScenarios => PortableConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(PortableConfigTransformationsScenarios))] + public async Task StartsWithWebConfigVariationsPortable(string scenario) + { + var action = PortableConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var expectedArguments = action(iisDeploymentParameters); + var result = await DeployAsync(iisDeploymentParameters); + Assert.Equal(expectedArguments, await result.HttpClient.GetStringAsync("/CommandLineArgs")); + } + + public static Dictionary> InitPortableWebConfigTransformations() + { + var dictionary = new Dictionary>(); + var pathWithSpace = "\u03c0 \u2260 3\u00b714"; + + dictionary.Add("App in bin subdirectory full path to dll using exec and quotes", + parameters => { + MoveApplication(parameters, "bin"); + parameters.TransformArguments((arguments, root) => "exec " + Path.Combine(root, "bin", arguments)); + return ""; + }); + + dictionary.Add("App in subdirectory with space", + parameters => { + MoveApplication(parameters, pathWithSpace); + parameters.TransformArguments((arguments, root) => Path.Combine(pathWithSpace, arguments)); + return ""; + }); + + dictionary.Add("App in subdirectory with space and full path to dll", + parameters => { + MoveApplication(parameters, pathWithSpace); + parameters.TransformArguments((arguments, root) => Path.Combine(root, pathWithSpace, arguments)); + return ""; + }); + + dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes", + parameters => { + MoveApplication(parameters, pathWithSpace); + parameters.TransformArguments((arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments"); + return "extra|arguments"; + }); + + dictionary.Add("App in bin subdirectory and quoted argument", + parameters => { + MoveApplication(parameters, "bin"); + parameters.TransformArguments((arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\""); + return "extra argument"; + }); + + dictionary.Add("App in bin subdirectory full path to dll", + parameters => { + MoveApplication(parameters, "bin"); + parameters.TransformArguments((arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments"); + return "extra|arguments"; + }); + return dictionary; + } + + + private static Dictionary> StandaloneConfigTransformations = InitStandaloneConfigTransformations(); + public static IEnumerable StandaloneConfigTransformationsScenarios => StandaloneConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(StandaloneConfigTransformationsScenarios))] + public async Task StartsWithWebConfigVariationsStandalone(string scenario) + { + var action = StandaloneConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + iisDeploymentParameters.ApplicationType = ApplicationType.Standalone; + var expectedArguments = action(iisDeploymentParameters); + var result = await DeployAsync(iisDeploymentParameters); + Assert.Equal(expectedArguments, await result.HttpClient.GetStringAsync("/CommandLineArgs")); + } + + public static Dictionary> InitStandaloneConfigTransformations() + { + var dictionary = new Dictionary>(); + var pathWithSpace = "\u03c0 \u2260 3\u00b714"; + + dictionary.Add("App in subdirectory", + parameters => { + MoveApplication(parameters, pathWithSpace); + parameters.TransformPath((path, root) => Path.Combine(pathWithSpace, path)); + parameters.TransformArguments((arguments, root) => "\"additional argument\""); + return "additional argument"; + }); + + dictionary.Add("App in bin subdirectory full path", + parameters => { + MoveApplication(parameters, pathWithSpace); + parameters.TransformPath((path, root) => Path.Combine(root, pathWithSpace, path)); + parameters.TransformArguments((arguments, root) => "additional arguments"); + return "additional|arguments"; + }); + + return dictionary; + } + + private static void MoveApplication( + IISDeploymentParameters parameters, + string subdirectory) + { + parameters.WebConfigActionList.Add((config, contentRoot) => + { + var source = new DirectoryInfo(contentRoot); + var subDirectoryPath = source.CreateSubdirectory(subdirectory); + + // Copy everything into a subfolder + Helpers.CopyFiles(source, subDirectoryPath, null); + // Cleanup files + foreach (var fileSystemInfo in source.GetFiles()) + { + fileSystemInfo.Delete(); + } + }); + } + + private async Task AssertSiteFailsToStartWithInProcessStaticContent(IISDeploymentResult deploymentResult) + { + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Contains("HTTP Error 500.0 - ANCM In-Process Handler Load Failure", await response.Content.ReadAsStringAsync()); + StopServer(); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs new file mode 100644 index 0000000000..03aa0e16e6 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Inprocess/SynchronousReadAndWriteTests.cs @@ -0,0 +1,197 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class SynchronousReadAndWriteTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public SynchronousReadAndWriteTests(IISTestSiteFixture fixture): base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ReadAndWriteSynchronously() + { + for (int i = 0; i < 100; i++) + { + var content = new StringContent(new string('a', 100000)); + var response = await _fixture.Client.PostAsync("ReadAndWriteSynchronously", content); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(expected: 110000, actual: responseText.Length); + } + } + + [ConditionalFact] + public async Task ReadAndWriteEcho() + { + var body = new string('a', 100000); + var content = new StringContent(body); + var response = await _fixture.Client.PostAsync("ReadAndWriteEcho", content); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(body, responseText); + } + + [ConditionalFact] + public async Task ReadAndWriteCopyToAsync() + { + var body = new string('a', 100000); + var content = new StringContent(body); + var response = await _fixture.Client.PostAsync("ReadAndWriteCopyToAsync", content); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(body, responseText); + } + + [ConditionalFact] + public async Task ReadAndWriteEchoTwice() + { + var requestBody = new string('a', 10000); + var content = new StringContent(requestBody); + var response = await _fixture.Client.PostAsync("ReadAndWriteEchoTwice", content); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(requestBody.Length * 2, responseText.Length); + } + + [ConditionalFact] + public async Task ReadSetHeaderWrite() + { + var body = "Body text"; + var content = new StringContent(body); + var response = await _fixture.Client.PostAsync("SetHeaderFromBody", content); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(body, response.Headers.GetValues("BodyAsString").Single()); + Assert.Equal(body, responseText); + } + + [ConditionalFact] + public async Task ReadAndWriteSlowConnection() + { + using (var connection = _fixture.CreateTestConnection()) + { + var testString = "hello world"; + var request = $"POST /ReadAndWriteSlowConnection HTTP/1.0\r\n" + + $"Content-Length: {testString.Length}\r\n" + + "Host: " + "localhost\r\n" + + "\r\n" + testString; + + foreach (var c in request) + { + await connection.Send(c.ToString()); + await Task.Delay(10); + } + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + await connection.ReceiveHeaders(); + + for (int i = 0; i < 100; i++) + { + foreach (var c in testString) + { + await connection.Receive(c.ToString()); + } + await Task.Delay(10); + } + await connection.WaitForConnectionClose(); + } + } + + [ConditionalFact] + public async Task ReadAndWriteInterleaved() + { + using (var connection = _fixture.CreateTestConnection()) + { + var requestLength = 0; + var messages = new List(); + for (var i = 1; i < 100; i++) + { + var message = i + Environment.NewLine; + requestLength += message.Length; + messages.Add(message); + } + + await connection.Send( + "POST /ReadAndWriteEchoLines HTTP/1.0", + $"Content-Length: {requestLength}", + "Host: localhost", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + await connection.ReceiveHeaders(); + + foreach (var message in messages) + { + await connection.Send(message); + await connection.Receive(message); + } + + await connection.Send("\r\n"); + await connection.WaitForConnectionClose(); + } + } + + [ConditionalFact] + public async Task ConsumePartialBody() + { + using (var connection = _fixture.CreateTestConnection()) + { + var message = "Hello"; + await connection.Send( + "POST /ReadPartialBody HTTP/1.1", + $"Content-Length: {100}", + "Host: localhost", + "Connection: close", + "", + ""); + + await connection.Send(message); + + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + + // This test can return both content length or chunked response + // depending on if appfunc managed to complete before write was + // issued + var headers = await connection.ReceiveHeaders(); + if (headers.Contains("Content-Length: 5")) + { + await connection.Receive("Hello"); + } + else + { + await connection.Receive( + "5", + message, + ""); + await connection.Receive( + "0", + "", + ""); + } + + await connection.WaitForConnectionClose(); + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/LogFileTests.cs b/src/IISIntegration/test/Common.FunctionalTests/LogFileTests.cs new file mode 100644 index 0000000000..1e15cde5c2 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/LogFileTests.cs @@ -0,0 +1,242 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class LoggingTests : LogFileTestBase + { + private readonly PublishedSitesFixture _fixture; + + public LoggingTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithAllApplicationTypes() + .WithAncmVersions(AncmVersion.AspNetCoreModuleV2) + .WithAllHostingModels(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task CheckStdoutLoggingToFile(TestVariant variant) + { + await CheckStdoutToFile(variant, "ConsoleWrite"); + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task CheckStdoutErrLoggingToFile(TestVariant variant) + { + await CheckStdoutToFile(variant, "ConsoleErrorWrite"); + } + + private async Task CheckStdoutToFile(TestVariant variant, string path) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.AssertStarts(deploymentResult, path); + + StopServer(); + + var contents = Helpers.ReadAllTextFromFile(Helpers.GetExpectedLogName(deploymentResult, _logFolderPath), Logger); + + Assert.Contains("TEST MESSAGE", contents); + } + + // Move to separate file + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task InvalidFilePathForLogs_ServerStillRuns(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogEnabled", "true")); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine("Q:", "std"))); + + var deploymentResult = await DeployAsync(deploymentParameters); + + await Helpers.AssertStarts(deploymentResult, "HelloWorld"); + + StopServer(); + if (variant.HostingModel == HostingModel.InProcess) + { + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not start stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070003"); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not stop stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070002"); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task StartupMessagesAreLoggedIntoDebugLogFile(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.HandlerSettings["debugLevel"] = "file"; + deploymentParameters.HandlerSettings["debugFile"] = "debug.txt"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + await deploymentResult.HttpClient.GetAsync("/"); + + AssertLogs(Path.Combine(deploymentResult.ContentRoot, "debug.txt")); + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task StartupMessagesAreLoggedIntoDefaultDebugLogFile(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.HandlerSettings["debugLevel"] = "file"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + await deploymentResult.HttpClient.GetAsync("/"); + + AssertLogs(Path.Combine(deploymentResult.ContentRoot, "aspnetcore-debug.log")); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [MemberData(nameof(TestVariants))] + public async Task StartupMessagesAreLoggedIntoDefaultDebugLogFileWhenEnabledWithEnvVar(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG"] = "file"; + // Add empty debugFile handler setting to prevent IIS deployer from overriding debug settings + deploymentParameters.HandlerSettings["debugFile"] = ""; + var deploymentResult = await DeployAsync(deploymentParameters); + + await deploymentResult.HttpClient.GetAsync("/"); + + AssertLogs(Path.Combine(deploymentResult.ContentRoot, "aspnetcore-debug.log")); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [MemberData(nameof(TestVariants))] + public async Task StartupMessagesLogFileSwitchedWhenLogFilePresentInWebConfig(TestVariant variant) + { + var firstTempFile = Path.GetTempFileName(); + var secondTempFile = Path.GetTempFileName(); + + try + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = firstTempFile; + deploymentParameters.AddDebugLogToWebConfig(secondTempFile); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/"); + + StopServer(); + var logContents = Helpers.ReadAllTextFromFile(firstTempFile, Logger); + + Assert.Contains("Switching debug log files to", logContents); + + AssertLogs(secondTempFile); + } + finally + { + File.Delete(firstTempFile); + File.Delete(secondTempFile); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + + public async Task DebugLogsAreWrittenToEventLog(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.HandlerSettings["debugLevel"] = "file,eventlog"; + var deploymentResult = await StartAsync(deploymentParameters); + StopServer(); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, @"\[aspnetcorev2.dll\] Initializing logs for .*?Description: IIS ASP.NET Core Module V2"); + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task CheckUTF8File(TestVariant variant) + { + var path = "CheckConsoleFunctions"; + + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, variant.HostingModel, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} {path}"); // For standalone this will need to remove space + + var logFolderPath = _logFolderPath + "\\彡⾔"; + deploymentParameters.EnableLogging(logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync(path); + + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + var contents = Helpers.ReadAllTextFromFile(Helpers.GetExpectedLogName(deploymentResult, logFolderPath), Logger); + Assert.Contains("彡⾔", contents); + + if (variant.HostingModel == HostingModel.InProcess) + { + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.InProcessThreadExitStdOut(deploymentResult, "12", "(.*)彡⾔(.*)")); + } + else + { + EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.OutOfProcessFailedToStart(deploymentResult)); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task OnlyOneFileCreatedWithProcessStartTime(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + await Helpers.AssertStarts(deploymentResult, "ConsoleWrite"); + + StopServer(); + + Assert.Single(Directory.GetFiles(_logFolderPath), Helpers.GetExpectedLogName(deploymentResult, _logFolderPath)); + } + + private static string ReadLogs(string logPath) + { + using (var stream = File.Open(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var streamReader = new StreamReader(stream)) + { + return streamReader.ReadToEnd(); + } + } + + private static void AssertLogs(string logPath) + { + var logContents = ReadLogs(logPath); + Assert.Contains("[aspnetcorev2.dll]", logContents); + Assert.True(logContents.Contains("[aspnetcorev2_inprocess.dll]") || logContents.Contains("[aspnetcorev2_outofprocess.dll]")); + Assert.Contains("Description: IIS ASP.NET Core Module V2. Commit:", logContents); + Assert.Contains("Description: IIS ASP.NET Core Module V2 Request Handler. Commit:", logContents); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/MultiApplicationTests.cs b/src/IISIntegration/test/Common.FunctionalTests/MultiApplicationTests.cs new file mode 100644 index 0000000000..3723369c89 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/MultiApplicationTests.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class MultiApplicationTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + private PublishedApplication _publishedApplication; + private PublishedApplication _rootApplication; + + public MultiApplicationTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task RunsTwoOutOfProcessApps() + { + var parameters = _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + parameters.ServerConfigActionList.Add(DuplicateApplication); + var result = await DeployAsync(parameters); + var id1 = await result.HttpClient.GetStringAsync("/app1/ProcessId"); + var id2 = await result.HttpClient.GetStringAsync("/app2/ProcessId"); + Assert.NotEqual(id2, id1); + } + + [ConditionalFact] + public async Task FailsAndLogsWhenRunningTwoInProcessApps() + { + var parameters = _fixture.GetBaseDeploymentParameters(HostingModel.InProcess, publish: true); + parameters.ServerConfigActionList.Add(DuplicateApplication); + + var result = await DeployAsync(parameters); + var result1 = await result.HttpClient.GetAsync("/app1/HelloWorld"); + var result2 = await result.HttpClient.GetAsync("/app2/HelloWorld"); + Assert.Equal(200, (int)result1.StatusCode); + Assert.Equal(500, (int)result2.StatusCode); + StopServer(); + EventLogHelpers.VerifyEventLogEvent(result, "Only one inprocess application is allowed per IIS application pool"); + } + + [ConditionalTheory] + [InlineData(HostingModel.OutOfProcess)] + [InlineData(HostingModel.InProcess)] + public async Task FailsAndLogsEventLogForMixedHostingModel(HostingModel firstApp) + { + var parameters = _fixture.GetBaseDeploymentParameters(firstApp, publish: true); + parameters.ServerConfigActionList.Add(DuplicateApplication); + var result = await DeployAsync(parameters); + + // Modify hosting model of other app to be the opposite + var otherApp = firstApp == HostingModel.InProcess ? HostingModel.OutOfProcess : HostingModel.InProcess; + SetHostingModel(_publishedApplication.Path, otherApp); + + var result1 = await result.HttpClient.GetAsync("/app1/HelloWorld"); + var result2 = await result.HttpClient.GetAsync("/app2/HelloWorld"); + Assert.Equal(200, (int)result1.StatusCode); + Assert.Equal(500, (int)result2.StatusCode); + StopServer(); + EventLogHelpers.VerifyEventLogEvent(result, "Mixed hosting model is not supported."); + } + + private void SetHostingModel(string directory, HostingModel model) + { + var webConfigLocation = GetWebConfigLocation(directory); + XDocument webConfig = XDocument.Load(webConfigLocation); + webConfig.Root + .Descendants("system.webServer") + .Single() + .GetOrAdd("aspNetCore") + .SetAttributeValue("hostingModel", model.ToString()); + webConfig.Save(webConfigLocation); + } + + private void DuplicateApplication(XElement config, string contentRoot) + { + var siteElement = config + .RequiredElement("system.applicationHost") + .RequiredElement("sites") + .RequiredElement("site"); + + var application = siteElement + .RequiredElement("application"); + + application.SetAttributeValue("path", "/app1"); + + var source = new DirectoryInfo(contentRoot); + + var destination = new DirectoryInfo(contentRoot + "anotherApp"); + destination.Create(); + Helpers.CopyFiles(source, destination, Logger); + + _publishedApplication = new PublishedApplication(destination.FullName, Logger); + + var newApplication = new XElement(application); + newApplication.SetAttributeValue("path", "/app2"); + newApplication.RequiredElement("virtualDirectory") + .SetAttributeValue("physicalPath", destination.FullName); + + siteElement.Add(newApplication); + + // IIS Express requires root application to exist + var rootApplicationDirectory = new DirectoryInfo(contentRoot + "rootApp"); + rootApplicationDirectory.Create(); + + _rootApplication = new PublishedApplication(rootApplicationDirectory.FullName, Logger); + File.WriteAllText(GetWebConfigLocation(rootApplicationDirectory.FullName), ""); + + var rootApplication = new XElement(application); + rootApplication.SetAttributeValue("path", "/"); + rootApplication.RequiredElement("virtualDirectory") + .SetAttributeValue("physicalPath", rootApplicationDirectory.FullName); + + siteElement.Add(rootApplication); + } + + private static string GetWebConfigLocation(string siteRoot) + { + return Path.Combine(siteRoot, "web.config"); + } + + public override void Dispose() + { + base.Dispose(); + _rootApplication.Dispose(); + _publishedApplication.Dispose(); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs new file mode 100644 index 0000000000..3b86662717 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs @@ -0,0 +1,125 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Xunit; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class AspNetCorePortTests : IISFunctionalTestBase + { + // Port range allowed by ANCM config + private const int _minPort = 1025; + private const int _maxPort = 48000; + + private static readonly Random _random = new Random(); + + private readonly PublishedSitesFixture _fixture; + + public AspNetCorePortTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithApplicationTypes(ApplicationType.Portable) + .WithAllAncmVersions(); + + public static IEnumerable InvalidTestVariants + => from v in TestVariants.Select(v => v.Single()) + from s in new string[] { (_minPort - 1).ToString(), (_maxPort + 1).ToString(), "noninteger" } + select new object[] { v, s }; + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task EnvVarInWebConfig_Valid(TestVariant variant) + { + // Must publish to set env vars in web.config + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + var port = GetUnusedRandomPort(); + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_PORT"] = port.ToString(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var responseText = await deploymentResult.HttpClient.GetStringAsync("/ServerAddresses"); + + Assert.Equal(port, new Uri(responseText).Port); + } + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task EnvVarInWebConfig_Empty(TestVariant variant) + { + // Must publish to set env vars in web.config + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_PORT"] = string.Empty; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var responseText = await deploymentResult.HttpClient.GetStringAsync("/ServerAddresses"); + + // If env var is empty, ANCM should assign a random port (same as no env var) + Assert.InRange(new Uri(responseText).Port, _minPort, _maxPort); + } + + [ConditionalTheory] + [MemberData(nameof(InvalidTestVariants))] + public async Task EnvVarInWebConfig_Invalid(TestVariant variant, string port) + { + // Must publish to set env vars in web.config + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_PORT"] = port; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/ServerAddresses"); + + Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode); + } + + private static int GetUnusedRandomPort() + { + // Large number of retries to prevent test failures due to port collisions, but not infinite + // to prevent infinite loop in case Bind() fails repeatedly for some other reason. + const int retries = 100; + + List exceptions = null; + + for (var i = 0; i < retries; i++) + { + var port = _random.Next(_minPort, _maxPort); + + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + try + { + socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + return port; + } + catch (Exception e) + { + // Bind failed, most likely because port is in use. Save exception and retry. + if (exceptions == null) + { + exceptions = new List(retries); + } + exceptions.Add(e); + } + } + } + + throw new AggregateException($"Unable to find unused random port after {retries} retries.", exceptions); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs new file mode 100644 index 0000000000..f9e6836b6f --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/GlobalVersionTests.cs @@ -0,0 +1,252 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class GlobalVersionTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public GlobalVersionTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + private const string _handlerVersion20 = "2.0.0"; + private const string _helloWorldRequest = "HelloWorld"; + private const string _helloWorldResponse = "Hello World"; + + [ConditionalFact] + public async Task GlobalVersion_DefaultWorks() + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + + deploymentParameters.HandlerSettings["handlerVersion"] = _handlerVersion20; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync(_helloWorldRequest); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(_helloWorldResponse, responseText); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewShim] + public async Task GlobalVersion_EnvironmentVariableWorks() + { + var temporaryFile = Path.GetTempFileName(); + try + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + CopyShimToOutput(deploymentParameters); + deploymentParameters.PublishApplicationBeforeDeployment = true; + deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER"] = temporaryFile; + + var deploymentResult = await DeployAsync(deploymentParameters); + var requestHandlerPath = Path.Combine(GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20), "aspnetcorev2_outofprocess.dll"); + + File.Delete(temporaryFile); + File.Move(requestHandlerPath, temporaryFile); + + var response = await deploymentResult.HttpClient.GetAsync(_helloWorldRequest); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(_helloWorldResponse, responseText); + StopServer(); + } + finally + { + File.Delete(temporaryFile); + } + } + + [ConditionalTheory] + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_NewVersionNumber_Fails(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["handlerVersion"] = version; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync(_helloWorldRequest); + Assert.False(response.IsSuccessStatusCode); + var responseString = await response.Content.ReadAsStringAsync(); + Assert.Contains("HTTP Error 500.0 - ANCM Out-Of-Process Handler Load Failure", responseString); + } + + [ConditionalTheory] + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_NewVersionNumber(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + CopyShimToOutput(deploymentParameters); + deploymentParameters.HandlerSettings["handlerVersion"] = version; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + var newANCMPath = GetANCMRequestHandlerPath(deploymentResult, version); + Directory.Move(originalANCMPath, newANCMPath); + + var response = await deploymentResult.HttpClient.GetAsync(_helloWorldRequest); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(_helloWorldResponse, responseText); + AssertLoadedVersion(version); + } + + [ConditionalTheory] + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_MultipleRequestHandlers_PicksHighestOne(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + CopyShimToOutput(deploymentParameters); + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + var newANCMPath = GetANCMRequestHandlerPath(deploymentResult, version); + + CopyDirectory(originalANCMPath, newANCMPath); + + deploymentResult.HttpClient.DefaultRequestHeaders.Add("ANCMRHPath", newANCMPath); + var response = await deploymentResult.HttpClient.GetAsync("CheckRequestHandlerVersion"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + AssertLoadedVersion(version); + } + + [ConditionalTheory] + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_MultipleRequestHandlers_UpgradeWorks(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + CopyShimToOutput(deploymentParameters); + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + deploymentResult.HttpClient.DefaultRequestHeaders.Add("ANCMRHPath", originalANCMPath); + var response = await deploymentResult.HttpClient.GetAsync("CheckRequestHandlerVersion"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + + StopServer(); + + deploymentResult = await DeployAsync(deploymentParameters); + + originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + var newANCMPath = GetANCMRequestHandlerPath(deploymentResult, version); + + CopyDirectory(originalANCMPath, newANCMPath); + + deploymentResult.HttpClient.DefaultRequestHeaders.Add("ANCMRHPath", newANCMPath); + response = await deploymentResult.HttpClient.GetAsync("CheckRequestHandlerVersion"); + responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + AssertLoadedVersion(version); + } + + [ConditionalFact] + public async Task DoesNotCrashWhenNoVersionsAvailable() + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + CopyShimToOutput(deploymentParameters); + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + Directory.Delete(originalANCMPath, true); + var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + } + + private IISDeploymentParameters GetGlobalVersionBaseDeploymentParameters() + { + return _fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess, publish: true); + } + + private void CopyDirectory(string from, string to) + { + var toInfo = new DirectoryInfo(to); + toInfo.Create(); + + foreach (var file in new DirectoryInfo(from).GetFiles()) + { + file.CopyTo(Path.Combine(toInfo.FullName, file.Name)); + } + } + + private string GetANCMRequestHandlerPath(IISDeploymentResult deploymentResult, string version) + { + return Path.Combine(deploymentResult.ContentRoot, + deploymentResult.DeploymentParameters.RuntimeArchitecture.ToString(), + version); + } + + private void AssertLoadedVersion(string version) + { + StopServer(); + Assert.Contains(TestSink.Writes, context => context.Message.Contains(version + @"\aspnetcorev2_outofprocess.dll")); + } + + private static void CopyShimToOutput(IISDeploymentParameters parameters) + { + parameters.AddServerConfigAction( + (config, contentRoot) => { + var moduleNodes = config.DescendantNodesAndSelf() + .OfType() + .Where(element => + element.Name == "add" && + element.Attribute("name")?.Value.StartsWith("AspNetCoreModule") == true && + element.Attribute("image") != null); + + var sourceDirectory = new DirectoryInfo(Path.GetDirectoryName(moduleNodes.First().Attribute("image").Value)); + var destinationDirectory = new DirectoryInfo(Path.Combine(contentRoot, sourceDirectory.Name)); + destinationDirectory.Create(); + foreach (var element in moduleNodes) + { + var imageAttribute = element.Attribute("image"); + imageAttribute.Value = imageAttribute.Value.Replace(sourceDirectory.FullName, destinationDirectory.FullName); + } + CopyFiles(sourceDirectory, destinationDirectory); + }); + } + + private static void CopyFiles(DirectoryInfo source, DirectoryInfo target) + { + foreach (DirectoryInfo directoryInfo in source.GetDirectories()) + { + CopyFiles(directoryInfo, target.CreateSubdirectory(directoryInfo.Name)); + } + + foreach (FileInfo fileInfo in source.GetFiles()) + { + var destFileName = Path.Combine(target.FullName, fileInfo.Name); + fileInfo.CopyTo(destFileName); + } + } + + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs new file mode 100644 index 0000000000..f6e36613d7 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class HelloWorldTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public HelloWorldTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22, Tfm.Net461) + .WithAllApplicationTypes() + .WithAllAncmVersions(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task HelloWorld(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.ServerConfigActionList.Add( + (element, _) => { + element + .RequiredElement("system.webServer") + .RequiredElement("security") + .RequiredElement("authentication") + .Element("windowsAuthentication") + ?.SetAttributeValue("enabled", "false"); + }); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal("Hello World", responseText); + + response = await deploymentResult.HttpClient.GetAsync("/Path/%3F%3F?query"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("/??", responseText); + + response = await deploymentResult.HttpClient.GetAsync("/Query/%3FPath?query?"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("?query?", responseText); + + response = await deploymentResult.HttpClient.GetAsync("/BodyLimit"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("null", responseText); + + response = await deploymentResult.HttpClient.GetAsync("/Auth"); + responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal("null", responseText); + + Assert.Equal( + $"ContentRootPath {deploymentResult.ContentRoot}" + Environment.NewLine + + $"WebRootPath {deploymentResult.ContentRoot}\\wwwroot" + Environment.NewLine + + $"CurrentDirectory {deploymentResult.ContentRoot}", + await deploymentResult.HttpClient.GetStringAsync("/HostingEnvironment")); + + var expectedDll = variant.AncmVersion == AncmVersion.AspNetCoreModule ? "aspnetcore.dll" : "aspnetcorev2.dll"; + Assert.Contains(deploymentResult.HostProcess.Modules.OfType(), m=> m.FileName.Contains(expectedDll)); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/PublishedSitesFixture.cs b/src/IISIntegration/test/Common.FunctionalTests/PublishedSitesFixture.cs new file mode 100644 index 0000000000..282ee26109 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/PublishedSitesFixture.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.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + /// + /// This type just maps collection names to available fixtures + /// + [CollectionDefinition(Name)] + public class PublishedSitesCollection : ICollectionFixture, ICollectionFixture + { + public const string Name = nameof(PublishedSitesCollection); + } + + public class PublishedSitesFixture : IDisposable + { + public CachingApplicationPublisher InProcessTestSite { get; } = new CachingApplicationPublisher(Helpers.GetInProcessTestSitesPath()); + public CachingApplicationPublisher OutOfProcessTestSite { get; } = new CachingApplicationPublisher(Helpers.GetOutOfProcessTestSitesPath()); + + public void Dispose() + { + InProcessTestSite.Dispose(); + OutOfProcessTestSite.Dispose(); + } + + public IISDeploymentParameters GetBaseDeploymentParameters(HostingModel hostingModel = HostingModel.InProcess, bool publish = false) + { + var publisher = hostingModel == HostingModel.InProcess ? InProcessTestSite : OutOfProcessTestSite; + return GetBaseDeploymentParameters(publisher, hostingModel, publish); + } + public IISDeploymentParameters GetBaseDeploymentParameters(TestVariant variant, bool publish = false) + { + var publisher = variant.HostingModel == HostingModel.InProcess ? InProcessTestSite : OutOfProcessTestSite; + return GetBaseDeploymentParameters(publisher, new DeploymentParameters(variant), publish); + } + + public IISDeploymentParameters GetBaseDeploymentParameters(ApplicationPublisher publisher, HostingModel hostingModel = HostingModel.InProcess, bool publish = false) + { + return GetBaseDeploymentParameters( + publisher, + new DeploymentParameters(publisher.ApplicationPath, DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + { + HostingModel = hostingModel, + TargetFramework = "netcoreapp2.2", + AncmVersion = AncmVersion.AspNetCoreModuleV2 + }, + publish); + } + + public IISDeploymentParameters GetBaseDeploymentParameters(ApplicationPublisher publisher, DeploymentParameters baseParameters, bool publish = false) + { + return new IISDeploymentParameters(baseParameters) + { + ApplicationPublisher = publisher, + ApplicationPath = publisher.ApplicationPath, + PublishApplicationBeforeDeployment = publish + }; + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/RequiresNewHandler.cs b/src/IISIntegration/test/Common.FunctionalTests/RequiresNewHandler.cs new file mode 100644 index 0000000000..cbe43ec0c7 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/RequiresNewHandler.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresNewHandlerAttribute : Attribute, ITestCondition + { + public bool IsMet => !DeployerSelector.IsForwardsCompatibilityTest; + + public string SkipReason => "Test verifies new behavior in the aspnetcorev2_inprocess.dll that isn't supported in earlier versions."; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/RequiresNewShim.cs b/src/IISIntegration/test/Common.FunctionalTests/RequiresNewShim.cs new file mode 100644 index 0000000000..b0bc50a83b --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/RequiresNewShim.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresNewShimAttribute : Attribute, ITestCondition + { + public bool IsMet => !DeployerSelector.IsBackwardsCompatiblityTest; + + public string SkipReason => "Test verifies new behavior in the aspnetcorev2.dll that isn't supported in earlier versions."; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/ServerAbortTests.cs b/src/IISIntegration/test/Common.FunctionalTests/ServerAbortTests.cs new file mode 100644 index 0000000000..8ebd70db12 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/ServerAbortTests.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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public abstract class ServerAbortTests: FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + [Collection(IISTestSiteCollection.Name)] + public class InProc: ServerAbortTests + { + public InProc(IISTestSiteFixture fixture) : base(fixture) { } + } + + [Collection(OutOfProcessTestSiteCollection.Name)] + public class OutOfProcess: ServerAbortTests + { + public OutOfProcess(OutOfProcessTestSiteFixture fixture) : base(fixture) { } + } + + [Collection(OutOfProcessV1TestSiteCollection.Name)] + public class OutOfProcessV1: ServerAbortTests + { + public OutOfProcessV1(OutOfProcessV1TestSiteFixture fixture) : base(fixture) { } + } + + protected ServerAbortTests(IISTestSiteFixture fixture) : base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ClosesConnectionOnServerAbort() + { + try + { + var response = await _fixture.Client.GetAsync("/Abort").DefaultTimeout(); + + // 502 is expected for outofproc but not for inproc + if (_fixture.DeploymentResult.DeploymentParameters.HostingModel == HostingModel.OutOfProcess) + { + Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode); + // 0x80072f78 ERROR_HTTP_INVALID_SERVER_RESPONSE The server returned an invalid or unrecognized response + Assert.Contains("0x80072f78", await response.Content.ReadAsStringAsync()); + } + else + { + Assert.True(false, "Should not reach here"); + } + } + catch (HttpRequestException) + { + // Connection reset is expected both for outofproc and inproc + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/SkipIfNotAdminAttribute.cs b/src/IISIntegration/test/Common.FunctionalTests/SkipIfNotAdminAttribute.cs new file mode 100644 index 0000000000..d2acb70415 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/SkipIfNotAdminAttribute.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Principal; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class SkipIfNotAdminAttribute : Attribute, ITestCondition + { + public bool IsMet + { + get + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + public string SkipReason => "The current process is not running as admin."; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/SkipVSTSAttribute.cs b/src/IISIntegration/test/Common.FunctionalTests/SkipVSTSAttribute.cs new file mode 100644 index 0000000000..8e88fc8c26 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/SkipVSTSAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class SkipInVSTSAttribute : Attribute, ITestCondition + { + public bool IsMet => string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SYSTEM_TASKDEFINITIONSURI")); + + public string SkipReason => "Running in VSTS"; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/AppVerifier.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/AppVerifier.cs new file mode 100644 index 0000000000..7984193e29 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/AppVerifier.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + public class AppVerifier + { + private static readonly TimeSpan AppVerifierCommandTimeout = TimeSpan.FromSeconds(5); + + public static IDisposable Disable(ServerType serverType, int code) + { + // Set in SetupTestEnvironment.ps1 + var enabledCodes = (Environment.GetEnvironmentVariable("APPVERIFIER_ENABLED_CODES") ?? "").Split(' '); + string processName; + switch (serverType) + { + case ServerType.IISExpress: + processName = "iisexpress.exe"; + break; + case ServerType.IIS: + processName = "w3wp.exe"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(serverType), serverType, null); + } + + if (!enabledCodes.Contains(code.ToString())) + { + return null; + } + + RunProcessAndWaitForExit("appverif.exe", $"-configure {code} -for {processName} -with ErrorReport=0", AppVerifierCommandTimeout); + return new AppVerifierToken(processName, code.ToString()); + } + + private static void RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout) + { + var startInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + }; + + var process = Process.Start(startInfo); + + if (!process.WaitForExit((int)timeout.TotalMilliseconds)) + { + process.Kill(); + } + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Exit code {process.ExitCode} when running {fileName} {arguments}. Stdout: {process.StandardOutput.ReadToEnd()} Stderr: {process.StandardError.ReadToEnd()}"); + } + } + + public class AppVerifierToken : IDisposable + { + private readonly string _processName; + + private readonly string _codes; + + public AppVerifierToken(string processName, string codes) + { + _processName = processName; + _codes = codes; + } + + public void Dispose() + { + // + RunProcessAndWaitForExit("appverif.exe", $"-configure {_codes} -for {_processName} -with ErrorReport={Environment.GetEnvironmentVariable("APPVERIFIER_LEVEL")}", AppVerifierCommandTimeout); + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs new file mode 100644 index 0000000000..78c77fd2ca --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs @@ -0,0 +1,210 @@ +// 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.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class EventLogHelpers + { + public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString) + { + Assert.True(deploymentResult.HostProcess.HasExited); + + var entries = GetEntries(deploymentResult); + AssertSingleEntry(expectedRegexMatchString, entries); + } + + public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, ILogger logger) + { + Assert.True(deploymentResult.HostProcess.HasExited); + + var entries = GetEntries(deploymentResult); + try + { + AssertSingleEntry(expectedRegexMatchString, entries); + } + catch (Exception ex) + { + foreach (var entry in entries) + { + logger.LogInformation(entry.Message); + } + throw ex; + } + } + + public static void VerifyEventLogEvents(IISDeploymentResult deploymentResult, params string[] expectedRegexMatchString) + { + Assert.True(deploymentResult.HostProcess.HasExited); + + var entries = GetEntries(deploymentResult).ToList(); + foreach (var regexString in expectedRegexMatchString) + { + var matchedEntries = AssertSingleEntry(regexString, entries); + + foreach (var matchedEntry in matchedEntries) + { + entries.Remove(matchedEntry); + } + } + + Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}"); + } + + private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable entries) + { + var expectedRegex = new Regex(regexString, RegexOptions.Singleline); + var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray(); + Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'"); + Assert.True(matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}"); + return matchedEntries; + } + + private static string FormatEntries(IEnumerable entries) + { + return string.Join(",", entries.Select(e => e.Message)); + } + + private static IEnumerable GetEntries(IISDeploymentResult deploymentResult) + { + var eventLog = new EventLog("Application"); + + // Eventlog is already sorted based on time of event in ascending time. + // Check results in reverse order. + var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}."; + + // Event log messages round down to the nearest second, so subtract a second + var processStartTime = deploymentResult.HostProcess.StartTime.AddSeconds(-1); + for (var i = eventLog.Entries.Count - 1; i >= 0; i--) + { + var eventLogEntry = eventLog.Entries[i]; + if (eventLogEntry.TimeGenerated < processStartTime) + { + // If event logs is older than the process start time, we didn't find a match. + break; + } + + if (eventLogEntry.ReplacementStrings == null || + eventLogEntry.ReplacementStrings.Length < 3) + { + continue; + } + + // ReplacementStings == EventData collection in EventLog + // This is unaffected if event providers are not registered correctly + if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) && + processIdString == eventLogEntry.ReplacementStrings[1]) + { + yield return eventLogEntry; + } + } + } + + private static string AncmVersionToMatch(IISDeploymentResult deploymentResult) + { + return "IIS " + + (deploymentResult.DeploymentParameters.ServerType == ServerType.IISExpress ? "Express " : "") + + "AspNetCore Module" + + (deploymentResult.DeploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? " V2" : ""); + } + + public static string Started(IISDeploymentResult deploymentResult) + { + if (deploymentResult.DeploymentParameters.HostingModel == HostingModel.InProcess) + { + return InProcessStarted(deploymentResult); + } + else + { + return OutOfProcessStarted(deploymentResult); + } + } + + public static string InProcessStarted(IISDeploymentResult deploymentResult) + { + return $"Application '{EscapedContentRoot(deploymentResult)}' started the coreclr in-process successfully"; + } + + public static string OutOfProcessStarted(IISDeploymentResult deploymentResult) + { + return $"Application '/LM/W3SVC/1/ROOT' started process '\\d+' successfully and process '\\d+' is listening on port '\\d+'."; + } + + public static string InProcessFailedToStart(IISDeploymentResult deploymentResult, string reason) + { + return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to load clr and managed application. {reason}"; + } + + public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason) + { + return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/.*?'."; + } + + public static string InProcessThreadException(IISDeploymentResult deploymentResult, string reason) + { + return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed exception{reason}"; + } + + public static string InProcessThreadExit(IISDeploymentResult deploymentResult, string code) + { + return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'."; + } + public static string InProcessThreadExitStdOut(IISDeploymentResult deploymentResult, string code, string output) + { + return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'. Last 4KB characters of captured stdout and stderr logs:\r\n{output}"; + } + + public static string FailedToStartApplication(IISDeploymentResult deploymentResult, string code) + { + return $"Failed to start application '/LM/W3SVC/1/ROOT', ErrorCode '{code}'."; + } + + public static string ConfigurationLoadError(IISDeploymentResult deploymentResult, string reason) + { + return $"Could not load configuration. Exception message: {reason}"; + } + + public static string OutOfProcessFailedToStart(IISDeploymentResult deploymentResult) + { + return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to start process with " + + $"commandline '(.*)' with multiple retries. " + + $"The last try of listening port is '(.*)'. See previous warnings for details."; + } + + public static string InProcessHostfxrInvalid(IISDeploymentResult deploymentResult) + { + return $"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '(.*)'."; + } + + public static string InProcessFailedToFindNativeDependencies(IISDeploymentResult deploymentResult) + { + return "Invoking hostfxr to find the inprocess request handler failed without finding any native dependencies. " + + "This most likely means the app is misconfigured, please check the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App that " + + "are targeted by the application and are installed on the machine."; + } + + public static string InProcessFailedToFindRequestHandler(IISDeploymentResult deploymentResult) + { + return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application."; + } + + private static string EscapedContentRoot(IISDeploymentResult deploymentResult) + { + var contentRoot = deploymentResult.ContentRoot; + if (!contentRoot.EndsWith('\\')) + { + contentRoot += '\\'; + } + return Regex.Escape(contentRoot); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs new file mode 100644 index 0000000000..1a1a4ce490 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.Logging.Testing; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + public class FunctionalTestsBase : LoggedTest + { + private const string DebugEnvironmentVariable = "ASPNETCORE_MODULE_DEBUG"; + + public FunctionalTestsBase(ITestOutputHelper output = null) : base(output) + { + } + + protected IISDeployerBase _deployer; + + protected ApplicationDeployer CreateDeployer(IISDeploymentParameters parameters) + { + if (parameters.ServerType == ServerType.IISExpress && + !parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable)) + { + parameters.EnvironmentVariables[DebugEnvironmentVariable] = "console"; + } + + return IISApplicationDeployerFactory.Create(parameters, LoggerFactory); + } + + protected virtual async Task DeployAsync(IISDeploymentParameters parameters) + { + _deployer = (IISDeployerBase)CreateDeployer(parameters); + return (IISDeploymentResult)await _deployer.DeployAsync(); + } + + protected virtual async Task StartAsync(IISDeploymentParameters parameters) + { + var result = await DeployAsync(parameters); + await result.AssertStarts(); + return result; + } + + public override void Dispose() + { + StopServer(false); + } + + public void StopServer(bool gracefulShutdown = true) + { + _deployer?.Dispose(gracefulShutdown); + _deployer = null; + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/Helpers.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/Helpers.cs new file mode 100644 index 0000000000..1e31126eab --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -0,0 +1,241 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public static class Helpers + { + private static readonly TimeSpan RetryRequestDelay = TimeSpan.FromMilliseconds(100); + private static readonly int RetryRequestCount = 10; + + public static string GetTestWebSitePath(string name) + { + return Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"),"test", "WebSites", name); + } + + public static string GetInProcessTestSitesPath() + { + return DeployerSelector.IsForwardsCompatibilityTest ? GetTestWebSitePath("InProcessForwardsCompatWebSite") : GetTestWebSitePath("InProcessWebSite"); + } + + public static string GetOutOfProcessTestSitesPath() => GetTestWebSitePath("OutOfProcessWebSite"); + + public static async Task AssertStarts(this IISDeploymentResult deploymentResult, string path = "/HelloWorld") + { + var response = await deploymentResult.HttpClient.GetAsync(path); + + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal("Hello World", responseText); + } + + public static async Task StressLoad(HttpClient httpClient, string path, Action action) + { + async Task RunRequests() + { + var connection = new HttpClient() { BaseAddress = httpClient.BaseAddress }; + + for (int j = 0; j < 10; j++) + { + var response = await connection.GetAsync(path); + action(response); + } + } + + List tasks = new List(); + for (int i = 0; i < 10; i++) + { + tasks.Add(Task.Run(RunRequests)); + } + + await Task.WhenAll(tasks); + } + + public static string GetFrebFolder(string folder, IISDeploymentResult result) + { + if (result.DeploymentParameters.ServerType == ServerType.IISExpress) + { + return Path.Combine(folder, result.DeploymentParameters.SiteName); + } + else + { + return Path.Combine(folder, "W3SVC1"); + } + } + + public static void CopyFiles(DirectoryInfo source, DirectoryInfo target, ILogger logger) + { + foreach (DirectoryInfo directoryInfo in source.GetDirectories()) + { + if (directoryInfo.FullName != target.FullName) + { + CopyFiles(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), logger); + } + } + logger?.LogDebug($"Processing {target.FullName}"); + foreach (FileInfo fileInfo in source.GetFiles()) + { + logger?.LogDebug($" Copying {fileInfo.Name}"); + var destFileName = Path.Combine(target.FullName, fileInfo.Name); + fileInfo.CopyTo(destFileName); + } + } + + public static void ModifyWebConfig(this DeploymentResult deploymentResult, Action action) + { + var webConfigPath = Path.Combine(deploymentResult.ContentRoot, "web.config"); + var document = XDocument.Load(webConfigPath); + action(document.Root); + document.Save(webConfigPath); + } + + public static Task RetryRequestAsync(this HttpClient client, string uri, Func predicate) + { + return RetryRequestAsync(client, uri, message => Task.FromResult(predicate(message))); + } + + public static async Task RetryRequestAsync(this HttpClient client, string uri, Func> predicate) + { + HttpResponseMessage response = await client.GetAsync(uri); + + for (var i = 0; i < RetryRequestCount && !await predicate(response); i++) + { + // Keep retrying until app_offline is present. + response = await client.GetAsync(uri); + await Task.Delay(RetryRequestDelay); + } + + if (!await predicate(response)) + { + throw new InvalidOperationException($"Didn't get response that satisfies predicate after {RetryRequestCount} retries"); + } + + return response; + } + + public static async Task Retry(Func func, int attempts, int msDelay) + { + var exceptions = new List(); + + for (var attempt = 0; attempt < attempts; attempt++) + { + try + { + await func(); + return; + } + catch (Exception e) + { + exceptions.Add(e); + } + await Task.Delay(msDelay); + } + + throw new AggregateException(exceptions); + } + + public static void AssertWorkerProcessStop(this IISDeploymentResult deploymentResult, int? timeout = null) + { + var hostProcess = deploymentResult.HostProcess; + Assert.True(hostProcess.WaitForExit(timeout ?? (int)TimeoutExtensions.DefaultTimeoutValue.TotalMilliseconds)); + + if (deploymentResult.DeploymentParameters.ServerType == ServerType.IISExpress) + { + Assert.Equal(0, hostProcess.ExitCode); + } + } + + + public static async Task AssertRecycledAsync(this IISDeploymentResult deploymentResult, Func verificationAction = null) + { + if (deploymentResult.DeploymentParameters.HostingModel != HostingModel.InProcess) + { + throw new NotSupportedException(); + } + + deploymentResult.AssertWorkerProcessStop(); + if (deploymentResult.DeploymentParameters.ServerType == ServerType.IIS) + { + verificationAction = verificationAction ?? (() => deploymentResult.AssertStarts()); + await verificationAction(); + } + } + + public static IEnumerable ToTheoryData(this Dictionary dictionary) + { + return dictionary.Keys.Select(k => new[] { k }); + } + + public static string GetExpectedLogName(IISDeploymentResult deploymentResult, string logFolderPath) + { + var startTime = deploymentResult.HostProcess.StartTime.ToUniversalTime(); + + if (deploymentResult.DeploymentParameters.HostingModel == HostingModel.InProcess) + { + return Path.Combine(logFolderPath, $"std_{startTime.Year}{startTime.Month:D2}" + + $"{startTime.Day:D2}{startTime.Hour:D2}" + + $"{startTime.Minute:D2}{startTime.Second:D2}_" + + $"{deploymentResult.HostProcess.Id}.log"); + } + else + { + return Directory.GetFiles(logFolderPath).Single(); + } + } + + public static void ModifyFrameworkVersionInRuntimeConfig(IISDeploymentResult deploymentResult) + { + var path = Path.Combine(deploymentResult.ContentRoot, "InProcessWebSite.runtimeconfig.json"); + dynamic depsFileContent = JsonConvert.DeserializeObject(File.ReadAllText(path)); + depsFileContent["runtimeOptions"]["framework"]["version"] = "2.9.9"; + var output = JsonConvert.SerializeObject(depsFileContent); + File.WriteAllText(path, output); + } + + public static void AllowNoLogs(this IISDeploymentResult deploymentResult) + { + File.AppendAllText( + Path.Combine(deploymentResult.DeploymentParameters.PublishedApplicationRootPath, "aspnetcore-debug.log"), + "Running test allowed log file to be empty." + Environment.NewLine); + } + + public static string ReadAllTextFromFile(string filename, ILogger logger) + { + try + { + return File.ReadAllText(filename); + } + catch (Exception ex) + { + // check if there is a dotnet.exe, iisexpress.exe, or w3wp.exe processes still running. + var hostingProcesses = Process.GetProcessesByName("dotnet") + .Concat(Process.GetProcessesByName("iisexpress")) + .Concat(Process.GetProcessesByName("w3wp")); + + logger.LogError($"Could not read file content. Exception message {ex.Message}"); + logger.LogError("Current hosting exes running:"); + + foreach (var hostingProcess in hostingProcesses) + { + logger.LogError($"{hostingProcess.ProcessName} pid: {hostingProcess.Id} hasExited: {hostingProcess.HasExited.ToString()}"); + } + throw ex; + } + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCapability.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCapability.cs new file mode 100644 index 0000000000..0a080bb702 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCapability.cs @@ -0,0 +1,22 @@ +// 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; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Flags] + public enum IISCapability + { + None = 0, + Websockets = 1, + WindowsAuthentication = 2, + PoolEnvironmentVariables = 4, + ShutdownToken = 8, + DynamicCompression = 16, + ApplicationInitialization = 32, + TracingModule = 64, + FailedRequestTracingModule = 128, + BasicAuthentication = 256 + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteCollection.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteCollection.cs new file mode 100644 index 0000000000..2c424943f3 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteCollection.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [CollectionDefinition(Name)] + public class IISCompressionSiteCollection : ICollectionFixture + { + public const string Name = nameof(IISCompressionSiteCollection); + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteFixture.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteFixture.cs new file mode 100644 index 0000000000..3aff68d11b --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISCompressionSiteFixture.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class IISCompressionSiteFixture : IISTestSiteFixture + { + public IISCompressionSiteFixture() : base(Configure) + { + } + + private static void Configure(IISDeploymentParameters deploymentParameters) + { + // Enable dynamic compression + deploymentParameters.ServerConfigActionList.Add( + (element, _) => { + var webServerElement = element + .RequiredElement("system.webServer"); + + webServerElement + .GetOrAdd("urlCompression") + .SetAttributeValue("doDynamicCompression", "true"); + + webServerElement + .GetOrAdd("httpCompression") + .GetOrAdd("dynamicTypes") + .GetOrAdd("add", "mimeType", "text/*") + .SetAttributeValue("enabled", "true"); + + }); + + deploymentParameters.EnableModule("DynamicCompressionModule", "%IIS_BIN%\\compdyn.dll"); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs new file mode 100644 index 0000000000..a20a5e2e0e --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities +{ + public class IISFunctionalTestBase : FunctionalTestsBase + { + public IISFunctionalTestBase(ITestOutputHelper output = null) : base(output) + { + } + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteCollection.cs similarity index 55% rename from src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs rename to src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteCollection.cs index 8d53affc98..562d63adbe 100644 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteCollection.cs @@ -13,4 +13,17 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { public const string Name = nameof(IISTestSiteCollection); } + + [CollectionDefinition(Name)] + public class OutOfProcessTestSiteCollection : ICollectionFixture + { + public const string Name = nameof(OutOfProcessTestSiteCollection); + } + + [CollectionDefinition(Name)] + public class OutOfProcessV1TestSiteCollection : ICollectionFixture + { + public const string Name = nameof(OutOfProcessV1TestSiteCollection); + } + } diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs new file mode 100644 index 0000000000..e8cfd8f641 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -0,0 +1,191 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class OutOfProcessTestSiteFixture : IISTestSiteFixture + { + public OutOfProcessTestSiteFixture() : base(Configure) + { + } + + private static void Configure(IISDeploymentParameters deploymentParameters) + { + deploymentParameters.ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(); + deploymentParameters.HostingModel = HostingModel.OutOfProcess; + } + } + + public class OutOfProcessV1TestSiteFixture : IISTestSiteFixture + { + public OutOfProcessV1TestSiteFixture() : base(Configure) + { + } + + private static void Configure(IISDeploymentParameters deploymentParameters) + { + deploymentParameters.ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(); + deploymentParameters.HostingModel = HostingModel.OutOfProcess; + deploymentParameters.AncmVersion = AncmVersion.AspNetCoreModule; + } + } + + public class IISTestSiteFixture : IDisposable + { + private ApplicationDeployer _deployer; + private ILoggerFactory _loggerFactory; + private ForwardingProvider _forwardingProvider; + private IISDeploymentResult _deploymentResult; + private readonly Action _configure; + + public IISTestSiteFixture() : this(_ => { }) + { + } + + public IISTestSiteFixture(Action configure) + { + var logging = AssemblyTestLog.ForAssembly(typeof(IISTestSiteFixture).Assembly); + _loggerFactory = logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture)); + + _forwardingProvider = new ForwardingProvider(); + _loggerFactory.AddProvider(_forwardingProvider); + + _configure = configure; + } + + public HttpClient Client => DeploymentResult.HttpClient; + public IISDeploymentResult DeploymentResult + { + get + { + EnsureInitialized(); + return _deploymentResult; + } + } + + public TestConnection CreateTestConnection() + { + return new TestConnection(Client.BaseAddress.Port); + } + + public void Dispose() + { + _deployer?.Dispose(); + } + + public void Attach(LoggedTest test) + { + if (_forwardingProvider.LoggerFactory != null) + { + throw new InvalidOperationException("Test instance is already attached to this fixture"); + } + + _forwardingProvider.LoggerFactory = test.LoggerFactory; + } + + public void Detach(LoggedTest test) + { + if (_forwardingProvider.LoggerFactory != test.LoggerFactory) + { + throw new InvalidOperationException("Different test is attached to this fixture"); + } + + _forwardingProvider.LoggerFactory = null; + } + + private void EnsureInitialized() + { + if (_deployer != null) + { + return; + } + + var deploymentParameters = new IISDeploymentParameters(Helpers.GetInProcessTestSitesPath(), + DeployerSelector.ServerType, + RuntimeFlavor.CoreClr, + RuntimeArchitecture.x64) + { + TargetFramework = Tfm.NetCoreApp22, + AncmVersion = AncmVersion.AspNetCoreModuleV2, + HostingModel = HostingModel.InProcess, + PublishApplicationBeforeDeployment = true, + }; + + _configure(deploymentParameters); + + _deployer = IISApplicationDeployerFactory.Create(deploymentParameters, _loggerFactory); + _deploymentResult = (IISDeploymentResult)_deployer.DeployAsync().Result; + } + + private class ForwardingProvider : ILoggerProvider + { + private readonly List _loggers = new List(); + + private ILoggerFactory _loggerFactory; + + public ILoggerFactory LoggerFactory + { + get => _loggerFactory; + set + { + + lock (_loggers) + { + _loggerFactory = value; + foreach (var logger in _loggers) + { + logger.Logger = _loggerFactory?.CreateLogger("FIXTURE:" + logger.Name); + } + } + } + } + + public void Dispose() + { + lock (_loggers) + { + _loggers.Clear(); + } + } + + public ILogger CreateLogger(string categoryName) + { + lock (_loggers) + { + var logger = new ForwardingLogger(categoryName); + _loggers.Add(logger); + return logger; + } + } + } + + internal class ForwardingLogger : ILogger + { + public ForwardingLogger(string name) + { + Name = name; + } + + public ILogger Logger { get; set; } + public string Name { get; set; } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + Logger?.Log(logLevel, eventId, state, exception, formatter); + } + + public bool IsEnabled(LogLevel logLevel) => Logger?.IsEnabled(logLevel) == true; + + public IDisposable BeginScope(TState state) => Logger?.BeginScope(state); + } + } + +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/LogFileTestBase.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/LogFileTestBase.cs new file mode 100644 index 0000000000..17885f3547 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/LogFileTestBase.cs @@ -0,0 +1,34 @@ +// 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.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities +{ + public class LogFileTestBase : IISFunctionalTestBase + { + protected string _logFolderPath; + + public LogFileTestBase(ITestOutputHelper output = null) : base(output) + { + _logFolderPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + } + public override void Dispose() + { + base.Dispose(); + if (Directory.Exists(_logFolderPath)) + { + Directory.Delete(_logFolderPath, true); + } + } + + public string GetLogFileContent(IISDeploymentResult deploymentResult) + { + return Helpers.ReadAllTextFromFile(Helpers.GetExpectedLogName(deploymentResult, _logFolderPath), Logger); + } + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/RequiresEnvironmentVariableAttribute.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/RequiresEnvironmentVariableAttribute.cs new file mode 100644 index 0000000000..d2749db547 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/RequiresEnvironmentVariableAttribute.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresEnvironmentVariableAttribute : Attribute, ITestCondition + { + private readonly string _name; + + public RequiresEnvironmentVariableAttribute(string name) + { + _name = name; + } + + public bool IsMet => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(_name)); + + public string SkipReason => $"Environment variable {_name} is required to run this test."; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/Utilities/SkipIfDebugAttribute.cs b/src/IISIntegration/test/Common.FunctionalTests/Utilities/SkipIfDebugAttribute.cs new file mode 100644 index 0000000000..6cdd725392 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/Utilities/SkipIfDebugAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class SkipIfDebugAttribute : Attribute, ITestCondition + { + public bool IsMet => + #if DEBUG + false; + #else + true; + #endif + + public string SkipReason => "Test cannot be run in Debug mode."; + } +} diff --git a/src/IISIntegration/test/Common.FunctionalTests/WindowsAuthTests.cs b/src/IISIntegration/test/Common.FunctionalTests/WindowsAuthTests.cs new file mode 100644 index 0000000000..8431b15801 --- /dev/null +++ b/src/IISIntegration/test/Common.FunctionalTests/WindowsAuthTests.cs @@ -0,0 +1,52 @@ +// 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.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class WindowsAuthTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public WindowsAuthTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22, Tfm.Net461) + .WithApplicationTypes(ApplicationType.Portable) + .WithAllAncmVersions() + .WithAllHostingModels(); + + [ConditionalTheory] + [RequiresIIS(IISCapability.WindowsAuthentication)] + [MemberData(nameof(TestVariants))] + public async Task WindowsAuthTest(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.SetAnonymousAuth(enabled: false); + deploymentParameters.SetWindowsAuth(); + + // The default in hosting sets windows auth to true. + var deploymentResult = await DeployAsync(deploymentParameters); + + var client = deploymentResult.CreateClient(new HttpClientHandler { UseDefaultCredentials = true }); + var response = await client.GetAsync("/Auth"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.StartsWith("Windows:", responseText); + Assert.Contains(Environment.UserName, responseText); + } + } +} diff --git a/src/IISIntegration/test/Common.Tests/Common.Tests.csproj b/src/IISIntegration/test/Common.Tests/Common.Tests.csproj new file mode 100644 index 0000000000..ede80732ee --- /dev/null +++ b/src/IISIntegration/test/Common.Tests/Common.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.2 + false + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/Common.Tests/Utilities/DisposableList.cs b/src/IISIntegration/test/Common.Tests/Utilities/DisposableList.cs new file mode 100644 index 0000000000..78f76e41c2 --- /dev/null +++ b/src/IISIntegration/test/Common.Tests/Utilities/DisposableList.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + public class DisposableList : List, IDisposable where T : IDisposable + { + public DisposableList() : base() { } + + public DisposableList(IEnumerable collection) : base(collection) { } + + public DisposableList(int capacity) : base(capacity) { } + + public void Dispose() + { + foreach (var item in this) + { + item?.Dispose(); + } + } + } +} diff --git a/src/IISIntegration/test/Common.Tests/Utilities/TestConnections.cs b/src/IISIntegration/test/Common.Tests/Utilities/TestConnections.cs new file mode 100644 index 0000000000..3b7a870cf3 --- /dev/null +++ b/src/IISIntegration/test/Common.Tests/Utilities/TestConnections.cs @@ -0,0 +1,252 @@ +// 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.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + /// + /// Summary description for TestConnection + /// + public class TestConnection : IDisposable + { + private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1); + + private readonly bool _ownsSocket; + private readonly Socket _socket; + private readonly NetworkStream _stream; + + public TestConnection(int port) + : this(port, AddressFamily.InterNetwork) + { + } + + public TestConnection(int port, AddressFamily addressFamily) + : this(CreateConnectedLoopbackSocket(port, addressFamily), ownsSocket: true) + { + } + + public TestConnection(Socket socket) + : this(socket, ownsSocket: false) + { + } + + private TestConnection(Socket socket, bool ownsSocket) + { + _ownsSocket = ownsSocket; + _socket = socket; + _stream = new NetworkStream(_socket, ownsSocket: false); + } + + public Socket Socket => _socket; + public Stream Stream => _stream; + + public void Dispose() + { + _stream.Dispose(); + + if (_ownsSocket) + { + _socket.Dispose(); + } + } + + public async Task Send(params string[] lines) + { + var bytes = Encoding.ASCII.GetBytes(string.Join("\r\n", lines)); + + for (var index = 0; index < bytes.Length; index++) + { + await _stream.WriteAsync(bytes, index, 1).ConfigureAwait(false); + await _stream.FlushAsync().ConfigureAwait(false); + // Re-add delay to help find socket input consumption bugs more consistently + //await Task.Delay(TimeSpan.FromMilliseconds(5)); + } + } + + public async Task ReadCharAsync() + { + var bytes = new byte[1]; + return (await _stream.ReadAsync(bytes, 0, 1) == 1) ? bytes[0] : -1; + } + + public async Task ReadLineAsync() + { + var builder = new StringBuilder(); + var current = await ReadCharAsync(); + while (current != '\r') + { + builder.Append((char)current); + current = await ReadCharAsync(); + } + + // Consume \n + await ReadCharAsync(); + + return builder.ToString(); + } + + public async Task> ReceiveChunk() + { + var length = int.Parse(await ReadLineAsync(), System.Globalization.NumberStyles.HexNumber); + + var bytes = await Receive(length); + + await ReadLineAsync(); + + return bytes; + } + + public async Task ReceiveChunk(string expected) + { + Assert.Equal(expected, Encoding.ASCII.GetString((await ReceiveChunk()).Span)); + } + + public async Task Receive(params string[] lines) + { + var expected = string.Join("\r\n", lines); + var actual = await Receive(expected.Length); + + Assert.Equal(expected, Encoding.ASCII.GetString(actual.Span)); + } + + private async Task> Receive(int length) + { + var actual = new byte[length]; + int offset = 0; + try + { + while (offset < length) + { + var task = _stream.ReadAsync(actual, offset, actual.Length - offset); + if (!Debugger.IsAttached) + { + task = task.TimeoutAfter(Timeout); + } + + var count = await task.ConfigureAwait(false); + if (count == 0) + { + break; + } + + offset += count; + } + } + catch (TimeoutException ex) when (offset != 0) + { + throw new TimeoutException( + $"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" + + $"Expected:{Environment.NewLine}{length} bytes of data{Environment.NewLine}{Environment.NewLine}" + + $"Actual:{Environment.NewLine}{Encoding.ASCII.GetString(actual, 0, offset)}{Environment.NewLine}", + ex); + } + + return actual.AsMemory(0, offset); + } + + public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024) + { + var actual = new byte[maxLineLength]; + var offset = 0; + + while (offset < maxLineLength) + { + // Read one char at a time so we don't read past the end of the line. + var task = _stream.ReadAsync(actual, offset, 1); + if (!Debugger.IsAttached) + { + Assert.True(task.Wait(4000), "timeout"); + } + var count = await task.ConfigureAwait(false); + if (count == 0) + { + break; + } + + Assert.True(count == 1); + offset++; + + if (actual[offset - 1] == '\n') + { + break; + } + } + + var actualLine = Encoding.ASCII.GetString(actual, 0, offset); + Assert.StartsWith(prefix, actualLine); + } + + public async Task ReceiveHeaders(params string[] lines) + { + List headers = new List(); + string line; + do + { + line = await ReadLineAsync(); + headers.Add(line); + } while (line != ""); + + foreach (var s in lines) + { + Assert.Contains(s, headers); + } + + return headers.ToArray(); + } + + public Task WaitForConnectionClose() + { + var tcs = new TaskCompletionSource(); + var eventArgs = new SocketAsyncEventArgs(); + eventArgs.SetBuffer(new byte[128], 0, 128); + eventArgs.Completed += ReceiveAsyncCompleted; + eventArgs.UserToken = tcs; + + if (!_socket.ReceiveAsync(eventArgs)) + { + ReceiveAsyncCompleted(this, eventArgs); + } + + return tcs.Task; + } + + private void ReceiveAsyncCompleted(object sender, SocketAsyncEventArgs e) + { + var tcs = (TaskCompletionSource)e.UserToken; + if (e.BytesTransferred == 0) + { + tcs.SetResult(null); + } + else + { + tcs.SetException(new IOException( + $"Expected connection close, received data instead: \"{Encoding.ASCII.GetString(e.Buffer, 0, e.BytesTransferred)}\"")); + } + } + + public static Socket CreateConnectedLoopbackSocket(int port, AddressFamily addressFamily) + { + if (addressFamily != AddressFamily.InterNetwork && addressFamily != AddressFamily.InterNetworkV6) + { + throw new ArgumentException($"TestConnection does not support address family of type {addressFamily}", nameof(addressFamily)); + } + + var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); + var address = addressFamily == AddressFamily.InterNetworkV6 + ? IPAddress.IPv6Loopback + : IPAddress.Loopback; + socket.Connect(new IPEndPoint(address, port)); + return socket; + } + } +} diff --git a/src/IISIntegration/test/Common.Tests/Utilities/TimeoutExtensions.cs b/src/IISIntegration/test/Common.Tests/Utilities/TimeoutExtensions.cs new file mode 100644 index 0000000000..ce7175dff9 --- /dev/null +++ b/src/IISIntegration/test/Common.Tests/Utilities/TimeoutExtensions.cs @@ -0,0 +1,26 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting +{ + + public static class TimeoutExtensions + { + public static TimeSpan DefaultTimeoutValue = TimeSpan.FromSeconds(300); + + public static Task DefaultTimeout(this Task task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1) + { + return task.TimeoutAfter(DefaultTimeoutValue, filePath, lineNumber); + } + + public static Task DefaultTimeout(this Task task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1) + { + return task.TimeoutAfter(DefaultTimeoutValue, filePath, lineNumber); + } + } +} diff --git a/src/IISIntegration/test/CommonLibTests/CommonLibTests.vcxproj b/src/IISIntegration/test/CommonLibTests/CommonLibTests.vcxproj new file mode 100644 index 0000000000..87dbd16675 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/CommonLibTests.vcxproj @@ -0,0 +1,196 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {1eac8125-1765-4e2d-8cbe-56dc98a1c8c1} + Win32Proj + 10.0.15063.0 + Application + v141 + Unicode + + + + + + + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + + + + + + + + + + + + + + + + + + {ec82302f-d2f0-4727-99d1-eabc0dd9dc3b} + + + {55494e58-e061-4c4c-a0a8-837008e72f85} + + + {09d9d1d6-2951-4e14-bc35-76a23cf9391a} + + + {1533e271-f61b-441b-8b74-59fb61df0552} + + + {cac1267b-8778-4257-aac6-caf481723b01} + + + {d57ea297-6dc2-4bc0-8c91-334863327863} + + + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + EnableFastChecks + MultiThreadedDebug + Level3 + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\RequestHandlerLib;..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\ + /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" + stdcpp17 + + + true + Console + ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\; + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + + + + NotUsing + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + true + EnableFastChecks + MultiThreadedDebug + Level3 + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\RequestHandlerLib;..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\ + /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" + stdcpp17 + + + true + Console + ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\; + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + + + + NotUsing + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + MultiThreaded + Level3 + ProgramDatabase + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\RequestHandlerLib;..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\ + /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" + stdcpp17 + + + true + Console + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + true + ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\$(Configuration)\; + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + + + + NotUsing + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).pch + stdafx.h + MultiThreaded + Level3 + ProgramDatabase + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\RequestHandlerLib;..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\ + /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" + stdcpp17 + + + true + Console + /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib %(AdditionalOptions) + true + true + ..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\x64\$(Configuration)\; + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;inprocessapplication.obj;inprocesshandler.obj;ahadmin.lib;Rpcrt4.lib;inprocessapplicationbase.obj;stdafx.obj;version.lib;inprocessoptions.obj;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + + + \ No newline at end of file diff --git a/src/IISIntegration/test/CommonLibTests/ConfigUtilityTests.cpp b/src/IISIntegration/test/CommonLibTests/ConfigUtilityTests.cpp new file mode 100644 index 0000000000..9b5bf6e9e6 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/ConfigUtilityTests.cpp @@ -0,0 +1,123 @@ +// 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. + +#include "stdafx.h" +#include "gmock/gmock.h" +using ::testing::_; +using ::testing::NiceMock; + +namespace ConfigUtilityTests +{ + using ::testing::Test; + + class ConfigUtilityTest : public Test + { + protected: + void TestHandlerVersion(std::wstring key, std::wstring value, std::wstring expected, HRESULT(*func)(IAppHostElement*, STRU&)) + { + IAppHostElement* retElement = NULL; + + STRU handlerVersion; + + // NiceMock removes warnings about "uninteresting calls", + auto element = std::make_unique>(); + auto innerElement = std::make_unique>(); + auto collection = std::make_unique>(); + auto nameElement = std::make_unique>(); + auto mockProperty = std::make_unique>(); + + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(innerElement.get()), testing::Return(S_OK))); + ON_CALL(*innerElement, get_Collection(_)) + .WillByDefault(testing::DoAll(testing::SetArgPointee<0>(collection.get()), testing::Return(S_OK))); + ON_CALL(*collection, get_Count(_)) + .WillByDefault(DoAll(testing::SetArgPointee<0>(1), testing::Return(S_OK))); + ON_CALL(*collection, get_Item(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nameElement.get()), testing::Return(S_OK))); + ON_CALL(*nameElement, GetPropertyByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(mockProperty.get()), testing::Return(S_OK))); + EXPECT_CALL(*mockProperty, get_StringValue(_)) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(key.c_str())), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(value.c_str())), testing::Return(S_OK))); + + HRESULT hr = func(element.get(), handlerVersion); + + EXPECT_EQ(hr, S_OK); + EXPECT_STREQ(handlerVersion.QueryStr(), expected.c_str()); + } + }; + + TEST_F(ConfigUtilityTest, CheckHandlerVersionKeysAndValues) + { + auto func = ConfigUtility::FindHandlerVersion; + TestHandlerVersion(L"handlerVersion", L"value", L"value", func); + TestHandlerVersion(L"handlerversion", L"value", L"value", func); + TestHandlerVersion(L"HandlerversioN", L"value", L"value", func); + TestHandlerVersion(L"randomvalue", L"value", L"", func); + TestHandlerVersion(L"", L"value", L"", func); + TestHandlerVersion(L"", L"", L"", func); + } + + TEST_F(ConfigUtilityTest, CheckDebugLogFile) + { + auto func = ConfigUtility::FindDebugFile; + + TestHandlerVersion(L"debugFile", L"value", L"value", func); + TestHandlerVersion(L"debugFILE", L"value", L"value", func); + } + + TEST_F(ConfigUtilityTest, CheckDebugLevel) + { + auto func = ConfigUtility::FindDebugLevel; + + TestHandlerVersion(L"debugLevel", L"value", L"value", func); + TestHandlerVersion(L"debugLEVEL", L"value", L"value", func); + } + + TEST(ConfigUtilityTestSingle, MultipleElements) + { + IAppHostElement* retElement = NULL; + STRU handlerVersion; + + auto element = std::make_unique>(); + auto innerElement = std::make_unique>(); + auto collection = std::make_unique>(); + auto nameElement = std::make_unique>(); + auto mockProperty = std::make_unique>(); + + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(innerElement.get()), testing::Return(S_OK))); + ON_CALL(*innerElement, get_Collection(_)) + .WillByDefault(testing::DoAll(testing::SetArgPointee<0>(collection.get()), testing::Return(S_OK))); + ON_CALL(*collection, get_Count(_)) + .WillByDefault(DoAll(testing::SetArgPointee<0>(2), testing::Return(S_OK))); + ON_CALL(*collection, get_Item(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nameElement.get()), testing::Return(S_OK))); + ON_CALL(*nameElement, GetPropertyByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(mockProperty.get()), testing::Return(S_OK))); + EXPECT_CALL(*mockProperty, get_StringValue(_)) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"key")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"handlerVersion")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value2")), testing::Return(S_OK))); + + HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), handlerVersion); + + EXPECT_EQ(hr, S_OK); + EXPECT_STREQ(handlerVersion.QueryStr(), L"value2"); + } + + TEST(ConfigUtilityTestSingle, IgnoresFailedGetElement) + { + STRU handlerVersion; + + auto element = std::make_unique>(); + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nullptr), testing::Return(HRESULT_FROM_WIN32( ERROR_INVALID_INDEX )))); + + HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), handlerVersion); + + EXPECT_EQ(hr, S_OK); + EXPECT_STREQ(handlerVersion.QueryStr(), L""); + } +} diff --git a/src/IISIntegration/test/CommonLibTests/FileOutputManagerTests.cpp b/src/IISIntegration/test/CommonLibTests/FileOutputManagerTests.cpp new file mode 100644 index 0000000000..66a9ff7f0b --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/FileOutputManagerTests.cpp @@ -0,0 +1,152 @@ +// 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. + +#include "stdafx.h" +#include "gtest/internal/gtest-port.h" +#include "FileOutputManager.h" + +class FileManagerWrapper +{ +public: + FileOutputManager* manager; + FileManagerWrapper(FileOutputManager* m) + : manager(m) + { + manager->Start(); + } + + ~FileManagerWrapper() + { + delete manager; + } +}; + +namespace FileOutManagerStartupTests +{ + using ::testing::Test; + class FileOutputManagerTest : public Test + { + protected: + void + Test(std::wstring fileNamePrefix, FILE* out) + { + PCWSTR expected = L"test"; + + auto tempDirectory = TempDirectory(); + FileOutputManager* pManager = new FileOutputManager(fileNamePrefix, tempDirectory.path()); + + { + FileManagerWrapper wrapper(pManager); + + wprintf(expected, out); + } + + for (auto & p : std::filesystem::directory_iterator(tempDirectory.path())) + { + std::wstring filename(p.path().filename()); + ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix); + + std::wstring content = Helpers::ReadFileContent(std::wstring(p.path())); + } + } + }; + + TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWritten) + { + Test(L"", stdout); + Test(L"log", stdout); + } + + TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWrittenErr) + { + Test(L"", stderr); + Test(L"log", stderr); + } +} + +namespace FileOutManagerOutputTests +{ + TEST(FileOutManagerOutputTest, StdOut) + { + PCWSTR expected = L"test"; + + auto tempDirectory = TempDirectory(); + + FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path()); + { + FileManagerWrapper wrapper(pManager); + + fwprintf(stdout, expected); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_FALSE(output.empty()); + + ASSERT_STREQ(output.c_str(), expected); + } + } + + TEST(FileOutManagerOutputTest, StdErr) + { + PCWSTR expected = L"test"; + + auto tempDirectory = TempDirectory(); + + FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path().c_str()); + { + FileManagerWrapper wrapper(pManager); + + fwprintf(stderr, expected); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_FALSE(output.empty()); + + ASSERT_STREQ(output.c_str(), expected); + } + } + + TEST(FileOutManagerOutputTest, CapAt30KB) + { + PCWSTR expected = L"hello world"; + + auto tempDirectory = TempDirectory(); + + FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path()); + { + FileManagerWrapper wrapper(pManager); + + for (int i = 0; i < 3000; i++) + { + wprintf(expected); + } + pManager->Stop(); + auto output = pManager->GetStdOutContent(); + ASSERT_FALSE(output.empty()); + + ASSERT_EQ(output.size(), 30000); + } + } + + TEST(FileOutManagerOutputTest, StartStopRestoresCorrectly) + { + PCWSTR expected = L"test"; + + auto tempDirectory = TempDirectory(); + + for (int i = 0; i < 10; i++) + { + FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path()); + { + FileManagerWrapper wrapper(pManager); + + wprintf(expected); + pManager->Stop(); + auto output = pManager->GetStdOutContent(); + ASSERT_FALSE(output.empty()); + + ASSERT_STREQ(output.c_str(), expected); + } + } + } +} diff --git a/src/IISIntegration/test/CommonLibTests/GlobalVersionTests.cpp b/src/IISIntegration/test/CommonLibTests/GlobalVersionTests.cpp new file mode 100644 index 0000000000..f38c9361d2 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/GlobalVersionTests.cpp @@ -0,0 +1,153 @@ +// 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. + +#include "stdafx.h" +#include "gtest/internal/gtest-port.h" + +namespace GlobalVersionTests +{ + using ::testing::Test; + namespace fs = std::filesystem; + + class GlobalVersionTest : public Test + { + protected: + void + RemoveFileNamePath(PCWSTR dllPath, PCWSTR expected) + { + std::wstring res = GlobalVersionUtility::RemoveFileNameFromFolderPath(dllPath); + EXPECT_STREQ(res.c_str(), expected); + } + }; + + TEST_F(GlobalVersionTest, RemovesPathCorrectly) + { + RemoveFileNamePath(L"test\\log.txt", L"test"); + RemoveFileNamePath(L"test\\log", L"test"); + RemoveFileNamePath(L"C:\\Program Files\\IIS\\aspnetcorev2.dll", L"C:\\Program Files\\IIS"); + RemoveFileNamePath(L"test\\log.txt", L"test"); + } + + TEST(GetRequestHandlerVersions, GetFolders) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / L"2.0.0")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.path().c_str()); + EXPECT_EQ(res.size(), 1); + EXPECT_EQ(res.at(0), fx_ver_t(2, 0, 0, std::wstring())); + } + + TEST(GetRequestHandlerVersions, GetFolderPreview) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / L"2.0.0-preview")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.path().c_str()); + EXPECT_EQ(res.size(), 1); + EXPECT_EQ(res.at(0), fx_ver_t(2, 0, 0, std::wstring(L"-preview"))); + } + + TEST(GetRequestHandlerVersions, GetFolderManyVersions) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / + L"2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / + L"1.9.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / + L"2.1.0")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.path().c_str()); + EXPECT_EQ(res.size(), 3); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(1, 9, 0, std::wstring())) != std::end(res)); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(2, 0, 0, std::wstring())) != std::end(res)); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(2, 1, 0, std::wstring())) != std::end(res)); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithSingleFolder) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.path().c_str()); + + EXPECT_STREQ(res.c_str(), L"2.0.0"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersions) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.path().c_str()); + + EXPECT_STREQ(res.c_str(), L"2.1.0"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersionsPreview) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.2.0-preview")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.path().c_str()); + + EXPECT_STREQ(res.c_str(), L"2.2.0-preview"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersionNoPreview) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.path().c_str()); + + EXPECT_STREQ(res.c_str(), L"2.1.0"); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionNoHandlerName) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.path().c_str(), L"", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath.path() / L"2.0.0\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionPreviewWins) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview")); + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.path().c_str(), L"", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath.path() / L"2.1.0-preview\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionSpecificVersion) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview")); + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.path().c_str(), L"2.0.0", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath.path() / L"2.0.0\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionSpecificPreview) + { + auto tempPath = TempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.1.0-preview")); + EXPECT_TRUE(fs::create_directories(tempPath.path() / "2.2.0")); + + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.path().c_str(), L"2.1.0-preview", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath.path() / L"2.1.0-preview\\aspnetcorev2_outofprocess.dll").c_str()); + } +} diff --git a/src/IISIntegration/test/CommonLibTests/Helpers.cpp b/src/IISIntegration/test/CommonLibTests/Helpers.cpp new file mode 100644 index 0000000000..ccca6cad5b --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/Helpers.cpp @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" + +std::wstring +Helpers::ReadFileContent(std::wstring file) +{ + std::wcout << file << std::endl; + + std::fstream t(file); + std::stringstream buffer; + buffer << t.rdbuf(); + + int nChars = MultiByteToWideChar(CP_ACP, 0, buffer.str().c_str(), -1, NULL, 0); + + std::wstring retVal(nChars, '\0'); + + MultiByteToWideChar(CP_UTF8, 0, buffer.str().c_str(), -1, retVal.data(), nChars); + + return retVal; +} + +TempDirectory::TempDirectory() +{ + UUID uuid; + UuidCreate(&uuid); + RPC_CSTR szUuid = NULL; + if (UuidToStringA(&uuid, &szUuid) == RPC_S_OK) + { + m_path = std::filesystem::temp_directory_path() / reinterpret_cast(szUuid); + RpcStringFreeA(&szUuid); + return; + } + throw std::exception("Cannot create temp directory"); +} + +TempDirectory::~TempDirectory() +{ + std::filesystem::remove_all(m_path); +} diff --git a/src/IISIntegration/test/CommonLibTests/Helpers.h b/src/IISIntegration/test/CommonLibTests/Helpers.h new file mode 100644 index 0000000000..67256966bb --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/Helpers.h @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +class Helpers +{ +public: + static + std::wstring + ReadFileContent(std::wstring file); +}; + +class TempDirectory +{ +public: + + TempDirectory(); + + ~TempDirectory(); + + std::filesystem::path path() const + { + return m_path; + } + +private: + std::filesystem::path m_path; +}; diff --git a/src/IISIntegration/test/CommonLibTests/PipeOutputManagerTests.cpp b/src/IISIntegration/test/CommonLibTests/PipeOutputManagerTests.cpp new file mode 100644 index 0000000000..385d6df9e0 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/PipeOutputManagerTests.cpp @@ -0,0 +1,162 @@ +// 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. + +#include "stdafx.h" +#include "gtest/internal/gtest-port.h" +#include "PipeOutputManager.h" + +class FileManagerWrapper +{ +public: + PipeOutputManager * manager; + FileManagerWrapper(PipeOutputManager* m) + : manager(m) + { + manager->Start(); + } + + ~FileManagerWrapper() + { + delete manager; + } +}; + +namespace PipeOutputManagerTests +{ + TEST(PipeManagerOutputTest, StdOut) + { + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(true); + + pManager->Start(); + fwprintf(stdout, expected); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_STREQ(output.c_str(), expected); + delete pManager; + } + + TEST(PipeManagerOutputTest, StdOutMultiToWide) + { + PipeOutputManager* pManager = new PipeOutputManager(true); + + pManager->Start(); + fprintf(stdout, "test"); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_STREQ(output.c_str(), L"test"); + delete pManager; + } + + TEST(PipeManagerOutputTest, StdErr) + { + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(); + + pManager->Start(); + fwprintf(stderr, expected); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_STREQ(output.c_str(), expected); + delete pManager; + } + + TEST(PipeManagerOutputTest, CheckMaxPipeSize) + { + std::wstring test; + for (int i = 0; i < 3000; i++) + { + test.append(L"hello world"); + } + + PipeOutputManager* pManager = new PipeOutputManager(); + + pManager->Start(); + wprintf(test.c_str()); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_EQ(output.size(), (DWORD)30000); + delete pManager; + } + + TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut) + { + auto m_fdPreviousStdOut = _dup(_fileno(stdout)); + auto m_fdPreviousStdErr = _dup(_fileno(stderr)); + + SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); + SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); + + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(); + pManager->Start(); + + _dup2(m_fdPreviousStdOut, _fileno(stdout)); + _dup2(m_fdPreviousStdErr, _fileno(stderr)); + + // Test will fail if we didn't redirect stdout back to a file descriptor. + // This is because gtest relies on console output to know if a test succeeded or failed. + // If the output still points to a file/pipe, the test (and all other tests after it) will fail. + delete pManager; + } + + TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks) + { + for (int i = 0; i < 10; i++) + { + auto stdoutBefore = _fileno(stdout); + auto stderrBefore = _fileno(stderr); + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(); + + pManager->Start(); + fwprintf(stdout, expected); + + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_STREQ(output.c_str(), expected); + ASSERT_EQ(stdoutBefore, _fileno(stdout)); + ASSERT_EQ(stderrBefore, _fileno(stderr)); + delete pManager; + } + // When this returns, we get an AV from gtest. + } + + TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr) + { + for (int i = 0; i < 10; i++) + { + auto stdoutBefore = _fileno(stdout); + auto stderrBefore = _fileno(stderr); + auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE); + PCWSTR expected = L"test"; + + PipeOutputManager* pManager = new PipeOutputManager(); + + pManager->Start(); + fwprintf(stderr, expected); + pManager->Stop(); + + auto output = pManager->GetStdOutContent(); + ASSERT_STREQ(output.c_str(), expected); + ASSERT_EQ(stdoutBefore, _fileno(stdout)); + + ASSERT_EQ(stderrBefore, _fileno(stderr)); + + delete pManager; + } + + wprintf(L"Hello!"); + } +} + diff --git a/src/IISIntegration/test/CommonLibTests/exception_handler_tests.cpp b/src/IISIntegration/test/CommonLibTests/exception_handler_tests.cpp new file mode 100644 index 0000000000..d0a9e97fdb --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/exception_handler_tests.cpp @@ -0,0 +1,49 @@ +// 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. + +#include "stdafx.h" + +TEST(CaughtExceptionHResult, ReturnsOutOfMemoryForBadAlloc) +{ + HRESULT hr; + try + { + throw std::bad_alloc(); + } + catch(...) + { + hr = CaughtExceptionHResult(); + } + + EXPECT_EQ(E_OUTOFMEMORY, hr); +} + +TEST(CaughtExceptionHResult, ReturnsValueForSystemError) +{ + HRESULT hr; + try + { + throw std::system_error(E_INVALIDARG, std::system_category()); + } + catch(...) + { + hr = CaughtExceptionHResult(); + } + + EXPECT_EQ(E_INVALIDARG, hr); +} + +TEST(CaughtExceptionHResult, ReturnsUhandledExceptionForOtherExceptions) +{ + HRESULT hr; + try + { + throw E_INVALIDARG; + } + catch(...) + { + hr = CaughtExceptionHResult(); + } + + EXPECT_EQ(HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION), hr); +} diff --git a/src/IISIntegration/test/CommonLibTests/fakeclasses.h b/src/IISIntegration/test/CommonLibTests/fakeclasses.h new file mode 100644 index 0000000000..a6f4a3e111 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/fakeclasses.h @@ -0,0 +1,190 @@ +// 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. + +#pragma once + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "InProcessOptions.h" + +class MockProperty : public IAppHostProperty +{ +public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Name, HRESULT(BSTR* pbstrValue)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Value, HRESULT(VARIANT * pVariant)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_Value, HRESULT(VARIANT value)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_StringValue, HRESULT(BSTR* pbstrValue)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Exception, HRESULT(IAppHostPropertyException ** ppException)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT * pValue)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT value)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostPropertySchema ** ppSchema)); +}; + +class MockCollection : public IAppHostElementCollection +{ +public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostCollectionSchema** pSchema)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Count, HRESULT(DWORD * dwordElem)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, get_Item, HRESULT(VARIANT cIndex, IAppHostElement ** ppElement)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, AddElement, HRESULT(IAppHostElement * pElement, INT cPosition)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, DeleteElement, HRESULT(VARIANT cIndex)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, CreateNewElement, HRESULT(BSTR bstrElementName, IAppHostElement** ppElement)); +}; + +class MockElement : public IAppHostElement +{ +public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Name, HRESULT(BSTR * pbstrName)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Collection, HRESULT(IAppHostElementCollection ** ppCollection)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Properties, HRESULT(IAppHostPropertyCollection ** ppProperties)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_ChildElements, HRESULT(IAppHostChildElementCollection ** ppElements)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT * pValue)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT value)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostElementSchema** pSchema)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetElementByName, HRESULT(BSTR bstrSubName, IAppHostElement ** ppElement)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetPropertyByName, HRESULT(BSTR bstrSubName, IAppHostProperty ** ppProperty)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Methods, HRESULT(IAppHostMethodCollection ** ppMethods)); +}; + +class MockHttpServer : public IHttpServer +{ + // Inherited via IHttpServer + virtual BOOL IsCommandLineLaunch(VOID) const override + { + return 0; + } + virtual PCWSTR GetAppPoolName(VOID) const override + { + return PCWSTR(); + } + virtual HRESULT AssociateWithThreadPool(HANDLE hHandle, LPOVERLAPPED_COMPLETION_ROUTINE completionRoutine) override + { + return E_NOTIMPL; + } + virtual VOID IncrementThreadCount(VOID) override + { + return VOID(); + } + virtual VOID DecrementThreadCount(VOID) override + { + return VOID(); + } + virtual VOID ReportUnhealthy(PCWSTR pszReasonString, HRESULT hrReason) override + { + return VOID(); + } + virtual VOID RecycleProcess(PCWSTR pszReason) override + { + return VOID(); + } + virtual IAppHostAdminManager * GetAdminManager(VOID) const override + { + return nullptr; + } + virtual HRESULT GetFileInfo(PCWSTR pszPhysicalPath, HANDLE hUserToken, PSID pSid, PCWSTR pszChangeNotificationPath, HANDLE hChangeNotificationToken, BOOL fCache, IHttpFileInfo ** ppFileInfo, IHttpTraceContext * pHttpTraceContext = NULL) override + { + return E_NOTIMPL; + } + virtual HRESULT FlushKernelCache(PCWSTR pszUrl) override + { + return E_NOTIMPL; + } + virtual HRESULT DoCacheOperation(CACHE_OPERATION cacheOperation, IHttpCacheKey * pCacheKey, IHttpCacheSpecificData ** ppCacheSpecificData, IHttpTraceContext * pHttpTraceContext = NULL) override + { + return E_NOTIMPL; + } + virtual GLOBAL_NOTIFICATION_STATUS NotifyCustomNotification(ICustomNotificationProvider * pCustomOutput) override + { + return GLOBAL_NOTIFICATION_STATUS(); + } + virtual IHttpPerfCounterInfo * GetPerfCounterInfo(VOID) override + { + return nullptr; + } + virtual VOID RecycleApplication(PCWSTR pszAppConfigPath) override + { + return VOID(); + } + virtual VOID NotifyConfigurationChange(PCWSTR pszPath) override + { + return VOID(); + } + virtual VOID NotifyFileChange(PCWSTR pszFileName) override + { + return VOID(); + } + virtual IDispensedHttpModuleContextContainer * DispenseContainer(VOID) override + { + return nullptr; + } + virtual HRESULT AddFragmentToCache(HTTP_DATA_CHUNK * pDataChunk, PCWSTR pszFragmentName) override + { + return E_NOTIMPL; + } + virtual HRESULT ReadFragmentFromCache(PCWSTR pszFragmentName, BYTE * pvBuffer, DWORD cbSize, DWORD * pcbCopied) override + { + return E_NOTIMPL; + } + virtual HRESULT RemoveFragmentFromCache(PCWSTR pszFragmentName) override + { + return E_NOTIMPL; + } + virtual HRESULT GetWorkerProcessSettings(IWpfSettings ** ppWorkerProcessSettings) override + { + return E_NOTIMPL; + } + virtual HRESULT GetProtocolManagerCustomInterface(PCWSTR pProtocolManagerDll, PCWSTR pProtocolManagerDllInitFunction, DWORD dwCustomInterfaceId, PVOID * ppCustomInterface) override + { + return E_NOTIMPL; + } + virtual BOOL SatisfiesPrecondition(PCWSTR pszPrecondition, BOOL * pfUnknownPrecondition = NULL) const override + { + return 0; + } + virtual IHttpTraceContext * GetTraceContext(VOID) const override + { + return nullptr; + } + virtual HRESULT RegisterFileChangeMonitor(PCWSTR pszPath, HANDLE hToken, IHttpFileMonitor ** ppFileMonitor) override + { + return E_NOTIMPL; + } + virtual HRESULT GetExtendedInterface(HTTP_SERVER_INTERFACE_VERSION version, PVOID * ppInterface) override + { + return E_NOTIMPL; + } +}; + + +class MockHttpApplication: public IHttpApplication +{ +public: + MOCK_CONST_METHOD0(GetApplicationPhysicalPath, PCWSTR ()); + MOCK_CONST_METHOD0(GetApplicationId, PCWSTR ()); + MOCK_CONST_METHOD0(GetAppConfigPath, PCWSTR ()); + MOCK_METHOD0(GetModuleContextContainer, IHttpModuleContextContainer* ()); +}; + +class MockInProcessOptions : public InProcessOptions +{ +public: + static + MockInProcessOptions* + CreateConfig() + { + return new MockInProcessOptions; + } +}; + diff --git a/src/IISIntegration/test/CommonLibTests/hostfxr_utility_tests.cpp b/src/IISIntegration/test/CommonLibTests/hostfxr_utility_tests.cpp new file mode 100644 index 0000000000..01c9541429 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/hostfxr_utility_tests.cpp @@ -0,0 +1,122 @@ +// 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. + +#include "stdafx.h" +#include +#include +#include +#include "hostfxr_utility.h" +#include "Environment.h" + +TEST(ParseHostFxrArguments, BasicHostFxrArguments) +{ + std::vector bstrArray; + + HOSTFXR_UTILITY::AppendArguments( + L"exec \"test.dll\"", // args + L"invalid", // physical path to application + bstrArray); // args array. + + EXPECT_EQ(2, bstrArray.size()); + ASSERT_STREQ(L"exec", bstrArray[0].c_str()); + ASSERT_STREQ(L"test.dll", bstrArray[1].c_str()); +} + +TEST(ParseHostFxrArguments, NoExecProvided) +{ + std::vector bstrArray; + + HOSTFXR_UTILITY::AppendArguments( + L"test.dll", // args + L"ignored", // physical path to application + bstrArray); // args array. + + EXPECT_EQ(1, bstrArray.size()); + ASSERT_STREQ(L"test.dll", bstrArray[0].c_str()); +} + +TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath) +{ + std::vector bstrArray; + // we need to use existing dll so let's use ntdll that we know exists everywhere + auto system32 = Environment::ExpandEnvironmentVariables(L"%WINDIR%\\System32"); + HOSTFXR_UTILITY::AppendArguments( + L"exec \"ntdll.dll\"", // args + system32, // physical path to application + bstrArray, // args array. + true); // expandDllPaths + + EXPECT_EQ(2, bstrArray.size()); + ASSERT_STREQ(L"exec", bstrArray[0].c_str()); + ASSERT_STREQ((system32 + L"\\ntdll.dll").c_str(), bstrArray[1].c_str()); +} + +TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs) +{ + std::vector bstrArray; + std::filesystem::path struHostFxrDllLocation; + std::filesystem::path struExeLocation; + + EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters( + L"dotnet", // processPath + L"some\\path", // application physical path, ignored. + L"", //arguments + struHostFxrDllLocation, + struExeLocation, + bstrArray), // args array. + InvalidOperationException); +} + +TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) +{ + STRU struAbsolutePathToDotnet; + BOOL fDotnetInProgramFiles; + BOOL is64Bit; + BOOL fIsWow64 = FALSE; + SYSTEM_INFO systemInfo; + IsWow64Process(GetCurrentProcess(), &fIsWow64); + if (fIsWow64) + { + is64Bit = FALSE; + } + else + { + GetNativeSystemInfo(&systemInfo); + is64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; + } + + if (is64Bit) + { + fDotnetInProgramFiles = std::filesystem::is_regular_file(L"C:/Program Files/dotnet/dotnet.exe"); + } + else + { + fDotnetInProgramFiles = std::filesystem::is_regular_file(L"C:/Program Files (x86)/dotnet/dotnet.exe"); + } + + auto dotnetPath = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles(); + if (fDotnetInProgramFiles) + { + EXPECT_TRUE(dotnetPath.has_value()); + } + else + { + EXPECT_FALSE(dotnetPath.has_value()); + } +} + +TEST(GetHostFxrArguments, InvalidParams) +{ + std::vector bstrArray; + std::filesystem::path struHostFxrDllLocation; + std::filesystem::path struExeLocation; + + EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters( + L"bogus", // processPath + L"", // application physical path, ignored. + L"ignored", //arguments + struHostFxrDllLocation, + struExeLocation, + bstrArray), // args array. + InvalidOperationException); +} diff --git a/src/IISIntegration/test/CommonLibTests/inprocess_application_tests.cpp b/src/IISIntegration/test/CommonLibTests/inprocess_application_tests.cpp new file mode 100644 index 0000000000..d2ec985723 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/inprocess_application_tests.cpp @@ -0,0 +1,85 @@ +// 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. + +#include "stdafx.h" + +#include +#include "inprocessapplication.h" +#include "fakeclasses.h" + +using ::testing::_; +using ::testing::NiceMock; + +// Externals defined in inprocess +BOOL g_fProcessDetach; +HANDLE g_hEventLog; + +namespace InprocessTests +{ + TEST(InProcessTest, NoNullRefForExePath) + { + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); + + std::wstring exePath(L"hello"); + + std::array parameters{ + {"InProcessExeLocation", exePath.data()} + }; + + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), parameters.data(), 1); + + ASSERT_STREQ(app->QueryExeLocation().c_str(), L"hello"); + } + + TEST(InProcessTest, GeneratesVirtualPath) + { + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"SECTION1/SECTION2/SECTION3/SECTION4/SECTION5")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), nullptr, 0); + + ASSERT_STREQ(app->QueryApplicationVirtualPath().c_str(), L"/SECTION5"); + } + + TEST(InProcessTest, GeneratesVirtualPathForDefaultApp) + { + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"SECTION1/SECTION2/SECTION3/SECTION4")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), nullptr, 0); + + ASSERT_STREQ(app->QueryApplicationVirtualPath().c_str(), L"/"); + } +} diff --git a/src/IISIntegration/test/CommonLibTests/main.cpp b/src/IISIntegration/test/CommonLibTests/main.cpp new file mode 100644 index 0000000000..1ad0a10ccd --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/main.cpp @@ -0,0 +1,12 @@ +// 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. + +#include "stdafx.h" + +DECLARE_DEBUG_PRINT_OBJECT2("tests", ASPNETCORE_DEBUG_FLAG_INFO | ASPNETCORE_DEBUG_FLAG_CONSOLE); + +int wmain(int argc, wchar_t* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + RUN_ALL_TESTS(); +} diff --git a/src/IISIntegration/test/CommonLibTests/stdafx.h b/src/IISIntegration/test/CommonLibTests/stdafx.h new file mode 100644 index 0000000000..4b9ac7cd27 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/stdafx.h @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "stringa.h" +#include "stringu.h" +#include "dbgutil.h" +#include "ahutil.h" +#include "multisz.h" +#include "multisza.h" +#include "base64.h" +#include +#include +#include +#include +#include + +#include "stringu.h" +#include "stringa.h" +#include "multisz.h" +#include "dbgutil.h" +#include "hashfn.h" + +#include "requesthandler_config.h" +#include "hostfxr_utility.h" +#include "config_utility.h" +#include "environmentvariablehash.h" +#include "iapplication.h" +#include "debugutil.h" +#include "requesthandler.h" +#include "resources.h" +#include "aspnetcore_msg.h" +#include "Helpers.h" +#include "GlobalVersionUtility.h" + +#undef assert // Macro redefinition in IISLib. +#include "gtest/gtest.h" +#include "fakeclasses.h" + diff --git a/src/IISIntegration/test/CommonLibTests/utility_tests.cpp b/src/IISIntegration/test/CommonLibTests/utility_tests.cpp new file mode 100644 index 0000000000..ee69d79054 --- /dev/null +++ b/src/IISIntegration/test/CommonLibTests/utility_tests.cpp @@ -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. + +#include "stdafx.h" +#include "Environment.h" +#include "StringHelpers.h" + +TEST(PassUnexpandedEnvString, ExpandsResult) +{ + HRESULT hr = S_OK; + PCWSTR unexpandedString = L"ANCM_TEST_ENV_VAR"; + PCWSTR unexpandedStringValue = L"foobar"; + STRU struExpandedString; + SetEnvironmentVariable(L"ANCM_TEST_ENV_VAR", unexpandedStringValue); + + hr = struExpandedString.CopyAndExpandEnvironmentStrings(L"%ANCM_TEST_ENV_VAR%"); + EXPECT_EQ(hr, S_OK); + EXPECT_STREQ(L"foobar", struExpandedString.QueryStr()); +} + +TEST(PassUnexpandedEnvString, LongStringExpandsResults) +{ + HRESULT hr = S_OK; + PCWSTR unexpandedString = L"ANCM_TEST_ENV_VAR_LONG"; + STRU struStringValue; + STACK_STRU(struExpandedString, MAX_PATH); + + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + + SetEnvironmentVariable(unexpandedString, struStringValue.QueryStr()); + + hr = struExpandedString.CopyAndExpandEnvironmentStrings(L"%ANCM_TEST_ENV_VAR_LONG%"); + EXPECT_EQ(hr, S_OK); + EXPECT_EQ(struStringValue.QueryCCH(), struExpandedString.QueryCCH()); + // The values are exactly the same, however EXPECT_EQ is returning false. + //EXPECT_EQ(struStringValue.QueryStr(), struExpandedString.QueryStr()); + EXPECT_STREQ(struStringValue.QueryStr(), struExpandedString.QueryStr()); +} + + +TEST(GetEnvironmentVariableValue, ReturnsCorrectLenght) +{ + SetEnvironmentVariable(L"RANDOM_ENV_VAR_1", L"test"); + + auto result = Environment::GetEnvironmentVariableValue(L"RANDOM_ENV_VAR_1"); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(result.value().length(), 4); + EXPECT_STREQ(result.value().c_str(), L"test"); +} + + +TEST(GetEnvironmentVariableValue, ReturnsNulloptWhenNotFound) +{ + auto result = Environment::GetEnvironmentVariableValue(L"RANDOM_ENV_VAR_2"); + EXPECT_FALSE(result.has_value()); +} + +TEST(CheckStringHelpers, FormatWithoutContentDoesNotIncreaseSizeString) +{ + std::string testString = "test"; + auto result = format(testString); + EXPECT_EQ(testString.size(), result.size()); +} + +TEST(CheckStringHelpers, FormatWithoutContentDoesNotIncreaseSizeWstring) +{ + std::wstring testString = L"test"; + auto result = format(testString); + EXPECT_EQ(testString.size(), result.size()); +} diff --git a/src/IISIntegration/test/Directory.Build.props b/src/IISIntegration/test/Directory.Build.props index 3a74fe4d2a..edfd666254 100644 --- a/src/IISIntegration/test/Directory.Build.props +++ b/src/IISIntegration/test/Directory.Build.props @@ -2,9 +2,11 @@ - netcoreapp2.1 + + netcoreapp2.2 $(DeveloperBuildTestTfms) - $(StandardTestTfms);netcoreapp2.0 + $(StandardTestTfms) $(StandardTestTfms);net461 diff --git a/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/BackwardsCompatibilityTests.cs b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/BackwardsCompatibilityTests.cs new file mode 100644 index 0000000000..1c05a2bc70 --- /dev/null +++ b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/BackwardsCompatibilityTests.cs @@ -0,0 +1,40 @@ +// 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.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class BackwardsCompatibilityTests : FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public BackwardsCompatibilityTests(IISTestSiteFixture fixture) : base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task CheckBackwardsCompatibilityIsUsed() + { + + var response = await _fixture.Client.GetAsync("/HelloWorld"); + var handles = _fixture.DeploymentResult.HostProcess.Modules; + + foreach (ProcessModule handle in handles) + { + if (handle.ModuleName == "aspnetcorev2.dll") + { + Assert.Equal("12.2.18287.0", handle.FileVersionInfo.FileVersion); + return; + } + } + throw new XunitException($"Could not find aspnetcorev2.dll loaded in process {_fixture.DeploymentResult.HostProcess.ProcessName}"); + } + } +} diff --git a/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/DeployerSelector.cs b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/DeployerSelector.cs new file mode 100644 index 0000000000..5c6f3739a4 --- /dev/null +++ b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/DeployerSelector.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IntegrationTesting; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public static class DeployerSelector + { + public static ServerType ServerType => ServerType.IIS; + public static bool IsBackwardsCompatiblityTest => true; + public static bool IsForwardsCompatibilityTest => false; + } +} diff --git a/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/IIS.BackwardsCompatibility.FunctionalTests.csproj b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/IIS.BackwardsCompatibility.FunctionalTests.csproj new file mode 100644 index 0000000000..c819a03ab1 --- /dev/null +++ b/src/IISIntegration/test/IIS.BackwardsCompatibility.FunctionalTests/IIS.BackwardsCompatibility.FunctionalTests.csproj @@ -0,0 +1,46 @@ + + + + netcoreapp2.2 + IISBackwardsCompatibility.FunctionalTests + True + + + + + + + + + + + False + + + False + + + False + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/DeployerSelector.cs b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/DeployerSelector.cs new file mode 100644 index 0000000000..bd7aabbe0f --- /dev/null +++ b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/DeployerSelector.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IntegrationTesting; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public static class DeployerSelector + { + public static ServerType ServerType => ServerType.IIS; + public static bool IsBackwardsCompatiblityTest => false; + public static bool IsForwardsCompatibilityTest => true; + } +} diff --git a/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/ForwardsCompatibilityTests.cs b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/ForwardsCompatibilityTests.cs new file mode 100644 index 0000000000..5f4ebb5608 --- /dev/null +++ b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/ForwardsCompatibilityTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + public class ForwardsCompatibilityTests : FixtureLoggedTest + { + private readonly IISTestSiteFixture _fixture; + + public ForwardsCompatibilityTests(IISTestSiteFixture fixture) : base(fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task CheckForwardsCompatibilityIsUsed() + { + var response = await _fixture.Client.GetAsync("/HelloWorld"); + var handles = _fixture.DeploymentResult.HostProcess.Modules; + foreach (ProcessModule handle in handles) + { + if (handle.ModuleName == "aspnetcorev2_inprocess.dll") + { + Assert.Equal("12.2.18287.0", handle.FileVersionInfo.FileVersion); + return; + } + } + throw new XunitException($"Could not find aspnetcorev2_inprocess.dll loaded in process {_fixture.DeploymentResult.HostProcess.ProcessName}"); + } + } +} diff --git a/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/IIS.ForwardsCompatibility.FunctionalTests.csproj b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/IIS.ForwardsCompatibility.FunctionalTests.csproj new file mode 100644 index 0000000000..929f6ec6b0 --- /dev/null +++ b/src/IISIntegration/test/IIS.ForwardsCompatibility.FunctionalTests/IIS.ForwardsCompatibility.FunctionalTests.csproj @@ -0,0 +1,45 @@ + + + + netcoreapp2.2 + IISForwardsCompatibility.FunctionalTests + True + + + + + + + + + + False + + + False + + + False + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/IIS.FunctionalTests/DeployerSelector.cs b/src/IISIntegration/test/IIS.FunctionalTests/DeployerSelector.cs new file mode 100644 index 0000000000..f2e1be321e --- /dev/null +++ b/src/IISIntegration/test/IIS.FunctionalTests/DeployerSelector.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IntegrationTesting; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public static class DeployerSelector + { + public static ServerType ServerType => ServerType.IIS; + public static bool IsBackwardsCompatiblityTest => false; + public static bool IsForwardsCompatibilityTest => false; + } +} diff --git a/src/IISIntegration/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj b/src/IISIntegration/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj new file mode 100644 index 0000000000..62dec62e60 --- /dev/null +++ b/src/IISIntegration/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj @@ -0,0 +1,46 @@ + + + + netcoreapp2.2 + IIS.FunctionalTests + True + + + + + + + + + + + False + + + False + + + False + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs b/src/IISIntegration/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs new file mode 100644 index 0000000000..35f10b13ab --- /dev/null +++ b/src/IISIntegration/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs @@ -0,0 +1,150 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Newtonsoft.Json; +using Xunit; + +namespace IIS.FunctionalTests.Inprocess +{ + [Collection(PublishedSitesCollection.Name)] + public class StdOutRedirectionTests : LogFileTestBase + { + private readonly PublishedSitesFixture _fixture; + + public StdOutRedirectionTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + [SkipIfDebug] + public async Task FrameworkNotFoundExceptionLogged_Pipe() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyFrameworkVersionInRuntimeConfig(deploymentResult); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, + "The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found."); + } + + [ConditionalFact] + [SkipIfDebug] + public async Task FrameworkNotFoundExceptionLogged_File() + { + var deploymentParameters = + _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyFrameworkVersionInRuntimeConfig(deploymentResult); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + var contents = Helpers.ReadAllTextFromFile(Helpers.GetExpectedLogName(deploymentResult, _logFolderPath), Logger); + var expectedString = "The specified framework 'Microsoft.NETCore.App', version '2.9.9' was not found."; + EventLogHelpers.VerifyEventLogEvent(deploymentResult, expectedString); + Assert.Contains(expectedString, contents); + } + + [ConditionalFact] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [SkipIfDebug] + public async Task EnableCoreHostTraceLogging_TwoLogFilesCreated() + { + var deploymentParameters = + _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.TransformArguments((a, _) => $"{a} CheckLargeStdOutWrites"); + + deploymentParameters.EnvironmentVariables["COREHOST_TRACE"] = "1"; + + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + var fileInDirectory = Directory.GetFiles(_logFolderPath).Single(); + var contents = Helpers.ReadAllTextFromFile(fileInDirectory, Logger); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr"); + Assert.Contains("Invoked hostfxr", contents); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [SkipIfDebug] + [InlineData("CheckLargeStdErrWrites")] + [InlineData("CheckLargeStdOutWrites")] + [InlineData("CheckOversizedStdErrWrites")] + [InlineData("CheckOversizedStdOutWrites")] + public async Task EnableCoreHostTraceLogging_PipeCaptureNativeLogs(string path) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.EnvironmentVariables["COREHOST_TRACE"] = "1"; + deploymentParameters.TransformArguments((a, _) => $"{a} {path}"); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr"); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [SkipIfDebug] + [InlineData("CheckLargeStdErrWrites")] + [InlineData("CheckLargeStdOutWrites")] + [InlineData("CheckOversizedStdErrWrites")] + [InlineData("CheckOversizedStdOutWrites")] + public async Task EnableCoreHostTraceLogging_FileCaptureNativeLogs(string path) + { + var deploymentParameters = + _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true); + deploymentParameters.EnvironmentVariables["COREHOST_TRACE"] = "1"; + deploymentParameters.TransformArguments((a, _) => $"{a} {path}"); + + deploymentParameters.EnableLogging(_logFolderPath); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.False(response.IsSuccessStatusCode); + + StopServer(); + + var fileInDirectory = Directory.GetFiles(_logFolderPath).First(); + var contents = Helpers.ReadAllTextFromFile(fileInDirectory, Logger); + + EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Invoked hostfxr"); + Assert.Contains("Invoked hostfxr", contents); + } + } +} diff --git a/src/IISIntegration/test/IIS.Shared.FunctionalTests/MofFileTests.cs b/src/IISIntegration/test/IIS.Shared.FunctionalTests/MofFileTests.cs new file mode 100644 index 0000000000..a054225ac5 --- /dev/null +++ b/src/IISIntegration/test/IIS.Shared.FunctionalTests/MofFileTests.cs @@ -0,0 +1,26 @@ +// 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.Diagnostics; +using System.IO; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace IIS.FunctionalTests +{ + public class MofFileTests + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [RequiresIIS(IISCapability.TracingModule)] + public void CheckMofFile() + { + var path = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "src", "aspnetcoremodulev2", "aspnetcore", "ancm.mof"); + var process = Process.Start("mofcomp.exe", path); + process.WaitForExit(); + Assert.Equal(0, process.ExitCode); + } + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs b/src/IISIntegration/test/IIS.Shared.FunctionalTests/Properties/AssemblyInfo.cs similarity index 55% rename from src/IISIntegration/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs rename to src/IISIntegration/test/IIS.Shared.FunctionalTests/Properties/AssemblyInfo.cs index 240f2d35c0..b26f48a815 100644 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs +++ b/src/IISIntegration/test/IIS.Shared.FunctionalTests/Properties/AssemblyInfo.cs @@ -1,8 +1,10 @@ // 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. -// All functional tests in this project require a version of IIS express with an updated schema +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.Extensions.Logging.Testing; using Xunit; -[assembly: Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests.IISExpressSupportsInProcessHosting] [assembly: CollectionBehavior(DisableTestParallelization = true)] +[assembly: RequiresIIS] +[assembly: ShortClassName] diff --git a/src/IISIntegration/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs b/src/IISIntegration/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs new file mode 100644 index 0000000000..cfbbf70486 --- /dev/null +++ b/src/IISIntegration/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs @@ -0,0 +1,150 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Xml.Linq; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Win32; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresIISAttribute : Attribute, ITestCondition + { + private static readonly (IISCapability Capability, string DllName)[] Modules = + { + (IISCapability.Websockets, "iiswsock.dll"), + (IISCapability.WindowsAuthentication, "authsspi.dll"), + (IISCapability.DynamicCompression, "compdyn.dll"), + (IISCapability.ApplicationInitialization, "warmup.dll"), + (IISCapability.TracingModule, "iisetw.dll"), + (IISCapability.FailedRequestTracingModule, "iisfreb.dll"), + (IISCapability.BasicAuthentication, "authbas.dll"), + }; + + private static readonly bool _isMetStatic; + private static readonly string _skipReasonStatic; + private static readonly bool _poolEnvironmentVariablesAvailable; + private static readonly IISCapability _modulesAvailable; + + static RequiresIISAttribute() + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_TEST_SKIP_IIS") == "true") + { + _skipReasonStatic = "Test skipped using ASPNETCORE_TEST_SKIP_IIS environment variable"; + return; + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _skipReasonStatic = "IIS tests can only be run on Windows"; + return; + } + + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + _skipReasonStatic += "The current console is not running as admin."; + return; + } + + if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe"))) + { + _skipReasonStatic += "The machine does not have IIS installed."; + return; + } + + var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml"); + + if (!File.Exists(ancmConfigPath)) + { + _skipReasonStatic = "IIS Schema is not installed."; + return; + } + + XDocument ancmConfig; + + try + { + ancmConfig = XDocument.Load(ancmConfigPath); + } + catch + { + _skipReasonStatic = "Could not read ANCM schema configuration"; + return; + } + + _isMetStatic = ancmConfig + .Root + .Descendants("attribute") + .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal)); + + _skipReasonStatic = _isMetStatic ? null : "IIS schema needs to be upgraded to support ANCM."; + + foreach (var module in Modules) + { + if (File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", module.DllName))) + { + _modulesAvailable |= module.Capability; + } + } + + var iisRegistryKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\InetStp", writable: false); + if (iisRegistryKey == null) + { + _poolEnvironmentVariablesAvailable = false; + } + else + { + var majorVersion = (int)iisRegistryKey.GetValue("MajorVersion", -1); + var minorVersion = (int)iisRegistryKey.GetValue("MinorVersion", -1); + var version = new Version(majorVersion, minorVersion); + _poolEnvironmentVariablesAvailable = version >= new Version(10, 0); + } + } + + public RequiresIISAttribute() + : this(IISCapability.None) { } + + public RequiresIISAttribute(IISCapability capabilities) + { + IsMet = _isMetStatic; + SkipReason = _skipReasonStatic; + if (capabilities.HasFlag(IISCapability.PoolEnvironmentVariables)) + { + IsMet &= _poolEnvironmentVariablesAvailable; + if (!_poolEnvironmentVariablesAvailable) + { + SkipReason += "The machine does allow for setting environment variables on application pools."; + } + } + + if (capabilities.HasFlag(IISCapability.ShutdownToken)) + { + IsMet = false; + SkipReason += "https://github.com/aspnet/IISIntegration/issues/1074"; + } + + foreach (var module in Modules) + { + if (capabilities.HasFlag(module.Capability)) + { + var available = _modulesAvailable.HasFlag(module.Capability); + IsMet &= available; + if (!available) + { + SkipReason += $"The machine does have {module.Capability} available."; + } + } + } + } + + public bool IsMet { get; } + public string SkipReason { get; } + } +} diff --git a/src/IISIntegration/test/IIS.Shared.FunctionalTests/ServicesTests.cs b/src/IISIntegration/test/IIS.Shared.FunctionalTests/ServicesTests.cs new file mode 100644 index 0000000000..875b5b13be --- /dev/null +++ b/src/IISIntegration/test/IIS.Shared.FunctionalTests/ServicesTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ApplicationInitializationTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public ApplicationInitializationTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.ApplicationInitialization)] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task ApplicationPreloadStartsApp(HostingModel hostingModel) + { + // This test often hits a memory leak in warmup.dll module, it has been reported to IIS team + using (AppVerifier.Disable(DeployerSelector.ServerType, 0x900)) + { + var baseDeploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true); + baseDeploymentParameters.TransformArguments( + (args, contentRoot) => $"{args} CreateFile \"{Path.Combine(contentRoot, "Started.txt")}\""); + EnablePreload(baseDeploymentParameters); + + var result = await DeployAsync(baseDeploymentParameters); + + await Helpers.Retry(async () => await File.ReadAllTextAsync(Path.Combine(result.ContentRoot, "Started.txt")), 10, 200); + StopServer(); + EventLogHelpers.VerifyEventLogEvent(result, EventLogHelpers.Started(result)); + } + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.ApplicationInitialization)] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task ApplicationInitializationPageIsRequested(HostingModel hostingModel) + { + // This test often hits a memory leak in warmup.dll module, it has been reported to IIS team + using (AppVerifier.Disable(DeployerSelector.ServerType, 0x900)) + { + var baseDeploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true); + EnablePreload(baseDeploymentParameters); + + baseDeploymentParameters.ServerConfigActionList.Add( + (config, _) => { + config + .RequiredElement("system.webServer") + .GetOrAdd("applicationInitialization") + .GetOrAdd("add", "initializationPage", "/CreateFile"); + }); + + var result = await DeployAsync(baseDeploymentParameters); + + await Helpers.Retry(async () => await File.ReadAllTextAsync(Path.Combine(result.ContentRoot, "Started.txt")), 10, 200); + StopServer(); + EventLogHelpers.VerifyEventLogEvent(result, EventLogHelpers.Started(result)); + } + } + + private static void EnablePreload(IISDeploymentParameters baseDeploymentParameters) + { + baseDeploymentParameters.EnsureSection("applicationInitialization", "system.webServer"); + baseDeploymentParameters.ServerConfigActionList.Add( + (config, _) => { + + config + .RequiredElement("system.applicationHost") + .RequiredElement("sites") + .RequiredElement("site") + .RequiredElement("application") + .SetAttributeValue("preloadEnabled", true); + }); + + baseDeploymentParameters.EnableModule("ApplicationInitializationModule", "%IIS_BIN%\\warmup.dll"); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/AppHostConfig/HostableWebCore.config b/src/IISIntegration/test/IIS.Tests/AppHostConfig/HostableWebCore.config new file mode 100644 index 0000000000..5e994d2855 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/AppHostConfig/HostableWebCore.config @@ -0,0 +1,198 @@ + + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/IIS.Tests/ClientDisconnectTests.cs b/src/IISIntegration/test/IIS.Tests/ClientDisconnectTests.cs new file mode 100644 index 0000000000..dbe562aa7c --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/ClientDisconnectTests.cs @@ -0,0 +1,302 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class ClientDisconnectTests : StrictTestServerTests + { + [ConditionalFact] + public async Task WritesSucceedAfterClientDisconnect() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var clientDisconnectedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + var data = new byte[1024]; + using (var testServer = await TestServer.Create( + async ctx => + { + requestStartedCompletionSource.SetResult(true); + await clientDisconnectedCompletionSource.Task; + for (var i = 0; i < 1000; i++) + { + await ctx.Response.Body.WriteAsync(data); + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.DefaultTimeout(); + } + clientDisconnectedCompletionSource.SetResult(true); + + await requestCompletedCompletionSource.Task.DefaultTimeout(); + } + + AssertConnectionDisconnectLog(); + } + + [ConditionalFact] + public async Task WritesCancelledWhenUsingAbortedToken() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + Exception exception = null; + + var data = new byte[1]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + while (true) + { + await ctx.Response.Body.WriteAsync(data, ctx.RequestAborted); + } + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + + await requestStartedCompletionSource.Task.DefaultTimeout(); + } + + await requestCompletedCompletionSource.Task.DefaultTimeout(); + + Assert.IsType(exception); + } + + AssertConnectionDisconnectLog(); + } + + [ConditionalFact] + public async Task ReadThrowsAfterClientDisconnect() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + Exception exception = null; + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + await ctx.Request.Body.ReadAsync(data); + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.DefaultTimeout(); + } + + await requestCompletedCompletionSource.Task.DefaultTimeout(); + } + + Assert.IsType(exception); + Assert.Equal("The client has disconnected", exception.Message); + + AssertConnectionDisconnectLog(); + } + + [ConditionalFact] + public async Task WriterThrowsCancelledException() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + Exception exception = null; + var cancellationTokenSource = new CancellationTokenSource(); + + var data = new byte[1]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + while (true) + { + await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token); + } + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + + await requestStartedCompletionSource.Task.DefaultTimeout(); + cancellationTokenSource.Cancel(); + await requestCompletedCompletionSource.Task.DefaultTimeout(); + } + + Assert.IsType(exception); + } + } + + [ConditionalFact] + public async Task ReaderThrowsCancelledException() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + Exception exception = null; + var cancellationTokenSource = new CancellationTokenSource(); + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + await ctx.Request.Body.ReadAsync(data, cancellationTokenSource.Token); + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStartedCompletionSource.Task.DefaultTimeout(); + cancellationTokenSource.Cancel(); + await requestCompletedCompletionSource.Task.DefaultTimeout(); + } + Assert.IsType(exception); + } + } + + [ConditionalFact] + public async Task ReaderThrowsResetExceptionOnInvalidBody() + { + var requestStartedCompletionSource = CreateTaskCompletionSource(); + var requestCompletedCompletionSource = CreateTaskCompletionSource(); + + Exception exception = null; + + var data = new byte[1024]; + using (var testServer = await TestServer.Create(async ctx => + { + requestStartedCompletionSource.SetResult(true); + try + { + await ctx.Request.Body.ReadAsync(data); + } + catch (Exception e) + { + exception = e; + } + + requestCompletedCompletionSource.SetResult(true); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await connection.Send( + "POST / HTTP/1.1", + "Transfer-Encoding: chunked", + "Host: localhost", + "Connection: close", + "", + ""); + + await requestStartedCompletionSource.Task; + await connection.Send( + "ZZZZZZZZZZZZZ"); + + await connection.Receive( + "HTTP/1.1 400 Bad Request", + "" + ); + + } + await requestCompletedCompletionSource.Task.DefaultTimeout(); + } + + Assert.IsType(exception); + Assert.Equal("The client has disconnected", exception.Message); + AssertConnectionDisconnectLog(); + } + + [ConditionalFact] + public async Task RequestAbortedIsTrippedWithoutIO() + { + var requestStarted = CreateTaskCompletionSource(); + var requestAborted = CreateTaskCompletionSource(); + + using (var testServer = await TestServer.Create( + async ctx => { + ctx.RequestAborted.Register(() => requestAborted.SetResult(true)); + requestStarted.SetResult(true); + await requestAborted.Task; + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await requestStarted.Task; + } + await requestAborted.Task; + } + + AssertConnectionDisconnectLog(); + } + + private void AssertConnectionDisconnectLog() + { + Assert.Single(TestSink.Writes, w => w.EventId.Name == "ConnectionDisconnect"); + } + + private static async Task SendContentLength1Post(TestConnection connection) + { + await connection.Send( + "POST / HTTP/1.1", + "Content-Length: 1", + "Host: localhost", + "Connection: close", + "", + ""); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/ConnectionIdFeatureTests.cs b/src/IISIntegration/test/IIS.Tests/ConnectionIdFeatureTests.cs new file mode 100644 index 0000000000..37e69b3a32 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/ConnectionIdFeatureTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class HttpBodyControlFeatureTests : StrictTestServerTests + { + [ConditionalFact] + public async Task ThrowsOnSyncReadOrWrite() + { + Exception writeException = null; + Exception readException = null; + using (var testServer = await TestServer.Create( + ctx => { + var bodyControl = ctx.Features.Get(); + bodyControl.AllowSynchronousIO = false; + + try + { + ctx.Response.Body.Write(new byte[10]); + } + catch (Exception ex) + { + writeException = ex; + } + + try + { + ctx.Request.Body.Read(new byte[10]); + } + catch (Exception ex) + { + readException = ex; + } + + return Task.CompletedTask; + }, LoggerFactory)) + { + await testServer.HttpClient.GetStringAsync("/"); + } + + Assert.IsType(readException); + Assert.IsType(writeException); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/HttpBodyControlFeatureTests.cs b/src/IISIntegration/test/IIS.Tests/HttpBodyControlFeatureTests.cs new file mode 100644 index 0000000000..3d91c445ca --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/HttpBodyControlFeatureTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class ConnectionIdFeatureTests : StrictTestServerTests + { + [ConditionalFact] + public async Task ProvidesConnectionId() + { + string connectionId = null; + using (var testServer = await TestServer.Create(ctx => { + var connectionIdFeature = ctx.Features.Get(); + connectionId = connectionIdFeature.ConnectionId; + return Task.CompletedTask; + }, LoggerFactory)) + { + await testServer.HttpClient.GetStringAsync("/"); + } + + Assert.NotNull(connectionId); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/IIS.Tests.csproj b/src/IISIntegration/test/IIS.Tests/IIS.Tests.csproj new file mode 100644 index 0000000000..3fdb2a5363 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/IIS.Tests.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/IIS.Tests/ResponseAbortTests.cs b/src/IISIntegration/test/IIS.Tests/ResponseAbortTests.cs new file mode 100644 index 0000000000..c7ec472586 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/ResponseAbortTests.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class ResponseAbortTests : StrictTestServerTests + { + [ConditionalFact] + public async Task ClosesWithoutSendingAnything() + { + using (var testServer = await TestServer.Create( + ctx => { + ctx.Abort(); + return Task.CompletedTask; + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await connection.WaitForConnectionClose(); + } + } + } + + [ConditionalFact] + public async Task ClosesAfterDataSent() + { + var bodyReceived = CreateTaskCompletionSource(); + using (var testServer = await TestServer.Create( + async ctx => { + await ctx.Response.WriteAsync("Abort"); + await ctx.Response.Body.FlushAsync(); + await bodyReceived.Task.DefaultTimeout(); + ctx.Abort(); + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await connection.Receive( + "HTTP/1.1 200 OK", + ""); + await connection.ReceiveHeaders( + "Transfer-Encoding: chunked"); + + await connection.ReceiveChunk("Abort"); + bodyReceived.SetResult(true); + await connection.WaitForConnectionClose(); + } + } + } + + [ConditionalFact] + public async Task ReadsThrowAfterAbort() + { + Exception exception = null; + + using (var testServer = await TestServer.Create( + async ctx => { + ctx.Abort(); + try + { + var a = new byte[10]; + await ctx.Request.Body.ReadAsync(a); + } + catch (Exception e) + { + exception = e; + } + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await connection.WaitForConnectionClose(); + } + } + + Assert.IsType(exception); + } + + [ConditionalFact] + public async Task WritesNoopAfterAbort() + { + Exception exception = null; + + using (var testServer = await TestServer.Create( + async ctx => { + ctx.Abort(); + try + { + await ctx.Response.Body.WriteAsync(new byte[10]); + } + catch (Exception e) + { + exception = e; + } + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await connection.WaitForConnectionClose(); + } + } + + Assert.Null(exception); + } + + [ConditionalFact] + public async Task RequestAbortedIsTrippedAfterAbort() + { + bool tokenAborted = false; + using (var testServer = await TestServer.Create( + ctx => { + ctx.Abort(); + tokenAborted = ctx.RequestAborted.IsCancellationRequested; + return Task.CompletedTask; + }, LoggerFactory)) + { + using (var connection = testServer.CreateConnection()) + { + await SendContentLength1Post(connection); + await connection.WaitForConnectionClose(); + } + } + + Assert.True(tokenAborted); + } + + private static async Task SendContentLength1Post(TestConnection connection) + { + await connection.Send( + "POST / HTTP/1.1", + "Content-Length: 1", + "Host: localhost", + "", + ""); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/StrictTestServerTests.cs b/src/IISIntegration/test/IIS.Tests/StrictTestServerTests.cs new file mode 100644 index 0000000000..c8f62beedb --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/StrictTestServerTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class StrictTestServerTests: LoggedTest + { + public override void Dispose() + { + base.Dispose(); + Assert.DoesNotContain(TestSink.Writes, w => w.LogLevel > LogLevel.Information); + } + + protected static TaskCompletionSource CreateTaskCompletionSource() + { + return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/TestServerTest.cs b/src/IISIntegration/test/IIS.Tests/TestServerTest.cs new file mode 100644 index 0000000000..40b538ea52 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/TestServerTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [SkipIfHostableWebCoreNotAvailable] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")] + public class TestServerTest : StrictTestServerTests + { + [ConditionalFact] + public async Task SingleProcessTestServer_HelloWorld() + { + var helloWorld = "Hello World"; + var expectedPath = "/Path"; + + string path = null; + using (var testServer = await TestServer.Create(ctx => + { + path = ctx.Request.Path.ToString(); + return ctx.Response.WriteAsync(helloWorld); + }, LoggerFactory)) + { + var result = await testServer.HttpClient.GetAsync(expectedPath); + Assert.Equal(helloWorld, await result.Content.ReadAsStringAsync()); + Assert.Equal(expectedPath, path); + } + } + } +} diff --git a/src/IISIntegration/test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs b/src/IISIntegration/test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs new file mode 100644 index 0000000000..b63743f106 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/Utilities/SkipIfHostableWebCoreNotAvailibleAttribute.cs @@ -0,0 +1,17 @@ +// 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.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class SkipIfHostableWebCoreNotAvailableAttribute : Attribute, ITestCondition + { + public bool IsMet { get; } = File.Exists(TestServer.HostableWebCoreLocation); + + public string SkipReason { get; } = $"Hostable Web Core not available, {TestServer.HostableWebCoreLocation} not found."; + } +} diff --git a/src/IISIntegration/test/IIS.Tests/Utilities/TestServer.cs b/src/IISIntegration/test/IIS.Tests/Utilities/TestServer.cs new file mode 100644 index 0000000000..351b826316 --- /dev/null +++ b/src/IISIntegration/test/IIS.Tests/Utilities/TestServer.cs @@ -0,0 +1,211 @@ +// 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.Net.Http; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using System.Xml.XPath; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class TestServer: IDisposable, IStartup + { + private const string InProcessHandlerDll = "aspnetcorev2_inprocess.dll"; + private const string AspNetCoreModuleDll = "aspnetcorev2.dll"; + private const string HWebCoreDll = "hwebcore.dll"; + + internal static string HostableWebCoreLocation => Environment.ExpandEnvironmentVariables($@"%windir%\system32\inetsrv\{HWebCoreDll}"); + internal static string BasePath => Path.GetDirectoryName(new Uri(typeof(TestServer).Assembly.CodeBase).AbsolutePath); + + internal static string AspNetCoreModuleLocation => Path.Combine(BasePath, AspNetCoreModuleDll); + + private static readonly SemaphoreSlim WebCoreLock = new SemaphoreSlim(1, 1); + + private static readonly int PortRetryCount = 10; + + private readonly TaskCompletionSource _startedTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + private readonly Action _appBuilder; + private readonly ILoggerFactory _loggerFactory; + private readonly hostfxr_main_fn _hostfxrMainFn; + + private Uri BaseUri => new Uri("http://localhost:" + _currentPort); + public HttpClient HttpClient { get; private set; } + public TestConnection CreateConnection() => new TestConnection(_currentPort); + + private IWebHost _host; + + private string _appHostConfigPath; + private int _currentPort; + + private TestServer(Action appBuilder, ILoggerFactory loggerFactory) + { + _hostfxrMainFn = Main; + _appBuilder = appBuilder; + _loggerFactory = loggerFactory; + } + + public static async Task Create(Action appBuilder, ILoggerFactory loggerFactory) + { + await WebCoreLock.WaitAsync(); + var server = new TestServer(appBuilder, loggerFactory); + server.Start(); + (await server.HttpClient.GetAsync("/start")).EnsureSuccessStatusCode(); + await server._startedTaskCompletionSource.Task; + return server; + } + + public static Task Create(RequestDelegate app, ILoggerFactory loggerFactory) + { + return Create(builder => builder.Run(app), loggerFactory); + } + + private void Start() + { + LoadLibrary(HostableWebCoreLocation); + _appHostConfigPath = Path.GetTempFileName(); + + set_main_handler(_hostfxrMainFn); + + Retry(() => + { + _currentPort = TestPortHelper.GetNextPort(); + + InitializeConfig(_currentPort); + + var startResult = WebCoreActivate(_appHostConfigPath, null, "Instance"); + if (startResult != 0) + { + throw new InvalidOperationException($"Error while running WebCoreActivate: {startResult} on port {_currentPort}"); + } + }, PortRetryCount); + + HttpClient = new HttpClient(new LoggingHandler(new SocketsHttpHandler(), _loggerFactory.CreateLogger())) + { + BaseAddress = BaseUri + }; + } + + private void InitializeConfig(int port) + { + var webHostConfig = XDocument.Load(Path.GetFullPath("HostableWebCore.config")); + webHostConfig.XPathSelectElement("/configuration/system.webServer/globalModules/add[@name='AspNetCoreModuleV2']") + .SetAttributeValue("image", AspNetCoreModuleLocation); + + var siteElement = webHostConfig.Root + .RequiredElement("system.applicationHost") + .RequiredElement("sites") + .RequiredElement("site"); + + siteElement + .RequiredElement("bindings") + .RequiredElement("binding") + .SetAttributeValue("bindingInformation", $":{port}:localhost"); + + webHostConfig.Save(_appHostConfigPath); + } + + private int Main(IntPtr argc, IntPtr argv) + { + _host = new WebHostBuilder() + .UseIIS() + .ConfigureServices(services => { + services.AddSingleton(this); + services.AddSingleton(_loggerFactory); + }) + .UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName) + .Build(); + + var doneEvent = new ManualResetEventSlim(); + var lifetime = _host.Services.GetService(); + + lifetime.ApplicationStopping.Register(() => doneEvent.Set()); + _host.Start(); + _startedTaskCompletionSource.SetResult(null); + doneEvent.Wait(); + _host.Dispose(); + return 0; + } + + public void Dispose() + { + HttpClient.Dispose(); + + // WebCoreShutdown occasionally AVs + // This causes the dotnet test process to crash + // To avoid this, we have to wait to shutdown + // and pass in true to immediately shutdown the hostable web core + // Both of these seem to be required. + Thread.Sleep(100); + WebCoreShutdown(immediate: true); + WebCoreLock.Release(); + } + + public IServiceProvider ConfigureServices(IServiceCollection services) + { + return services.BuildServiceProvider(); + } + + public void Configure(IApplicationBuilder app) + { + app.Map("/start", builder => builder.Run(context => context.Response.WriteAsync("Done"))); + _appBuilder(app); + } + + private delegate int hostfxr_main_fn(IntPtr argc, IntPtr argv); + + [DllImport(HWebCoreDll)] + private static extern int WebCoreActivate( + [In, MarshalAs(UnmanagedType.LPWStr)] + string appHostConfigPath, + [In, MarshalAs(UnmanagedType.LPWStr)] + string rootWebConfigPath, + [In, MarshalAs(UnmanagedType.LPWStr)] + string instanceName); + + [DllImport(HWebCoreDll)] + private static extern int WebCoreShutdown(bool immediate); + + [DllImport(InProcessHandlerDll)] + private static extern int set_main_handler(hostfxr_main_fn main); + + [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] + private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); + + private void Retry(Action func, int attempts) + { + var exceptions = new List(); + + for (var attempt = 0; attempt < attempts; attempt++) + { + try + { + func(); + return; + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + throw new AggregateException(exceptions); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/DeployerSelector.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/DeployerSelector.cs new file mode 100644 index 0000000000..ba6a1ec6e2 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/DeployerSelector.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IntegrationTesting; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public static class DeployerSelector + { + public static ServerType ServerType => ServerType.IISExpress; + public static bool IsBackwardsCompatiblityTest => false; + public static bool IsForwardsCompatibilityTest => false; + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/HttpsTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/HttpsTests.cs new file mode 100644 index 0000000000..ac58f73c0e --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/HttpsTests.cs @@ -0,0 +1,60 @@ +// 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.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class HttpsTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public HttpsTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22, Tfm.Net461) + .WithAllApplicationTypes() + .WithAllAncmVersions() + .WithAllHostingModels(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task HttpsHelloWorld(TestVariant variant) + { + var port = TestPortHelper.GetNextSSLPort(); + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; + deploymentParameters.AddHttpsToServerConfig(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (a, b, c, d) => true + }; + var client = deploymentResult.CreateClient(handler); + var response = await client.GetAsync("HttpsHelloWorld"); + var responseText = await response.Content.ReadAsStringAsync(); + if (variant.HostingModel == HostingModel.OutOfProcess) + { + Assert.Equal("Scheme:https; Original:http", responseText); + } + else + { + Assert.Equal("Scheme:https; Original:", responseText); + } + } + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj b/src/IISIntegration/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj similarity index 51% rename from src/IISIntegration/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj rename to src/IISIntegration/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj index d79a5ec5d9..988c2d5943 100644 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj @@ -1,27 +1,36 @@  - $(StandardTestTfms) + netcoreapp2.2 + True - + - - + + + False + + + + + + + - - + + diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs new file mode 100644 index 0000000000..47fb3a6553 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs @@ -0,0 +1,70 @@ +// 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.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class AuthenticationTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public AuthenticationTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + [RequiresIIS(IISCapability.WindowsAuthentication)] + public async Task Authentication_InProcess() + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.SetWindowsAuth(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/AuthenticationAnonymous"); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Anonymous?True", responseText); + + response = await deploymentResult.HttpClient.GetAsync("/AuthenticationRestricted"); + 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()); + + response = await deploymentResult.HttpClient.GetAsync("/AuthenticationRestrictedNTLM"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + // Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves. + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); + + response = await deploymentResult.HttpClient.GetAsync("/AuthenticationForbidden"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + + var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true }; + var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); + + response = await httpClient.GetAsync("/AuthenticationAnonymous"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Anonymous?True", responseText); + + response = await httpClient.GetAsync("/AuthenticationRestricted"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotEmpty(responseText); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs new file mode 100644 index 0000000000..caaf728c64 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ShutdownTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public ShutdownTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public async Task ServerShutsDownWhenMainExits() + { + var parameters = _fixture.GetBaseDeploymentParameters(publish: true); + var deploymentResult = await DeployAsync(parameters); + try + { + await deploymentResult.HttpClient.GetAsync("/Shutdown"); + } + catch (HttpRequestException ex) when (ex.InnerException is IOException) + { + // Server might close a connection before request completes + } + + deploymentResult.AssertWorkerProcessStop(); + } + + + [ConditionalFact] + public async Task ServerShutsDownWhenMainExitsStress() + { + var parameters = _fixture.GetBaseDeploymentParameters(publish: true); + var deploymentResult = await StartAsync(parameters); + + var load = Helpers.StressLoad(deploymentResult.HttpClient, "/HelloWorld", response => { + var statusCode = (int)response.StatusCode; + Assert.True(statusCode == 200 || statusCode == 503, "Status code was " + statusCode); + }); + + try + { + await deploymentResult.HttpClient.GetAsync("/Shutdown"); + await load; + } + catch (HttpRequestException ex) when (ex.InnerException is IOException | ex.InnerException is SocketException) + { + // Server might close a connection before request completes + } + + deploymentResult.AssertWorkerProcessStop(); + } + + [ConditionalFact] + public async Task GracefulShutdown_DoesNotCrashProcess() + { + var parameters = _fixture.GetBaseDeploymentParameters(publish: true); + var result = await DeployAsync(parameters); + + var response = await result.HttpClient.GetAsync("/HelloWorld"); + StopServer(gracefulShutdown: true); + Assert.True(result.HostProcess.ExitCode == 0); + } + + [ConditionalFact] + public async Task ForcefulShutdown_DoesCrashProcess() + { + var parameters = _fixture.GetBaseDeploymentParameters(publish: true); + var result = await DeployAsync(parameters); + + var response = await result.HttpClient.GetAsync("/HelloWorld"); + StopServer(gracefulShutdown: false); + Assert.True(result.HostProcess.ExitCode == 1); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs new file mode 100644 index 0000000000..826001c96d --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/InProcess/WebSocketTests.cs @@ -0,0 +1,79 @@ +// 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.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(IISTestSiteCollection.Name)] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "No supported on this platform")] + public class WebSocketsTests + { + private readonly string _webSocketUri; + + public WebSocketsTests(IISTestSiteFixture fixture) + { + _webSocketUri = fixture.DeploymentResult.ApplicationBaseUri.Replace("http:", "ws:"); + } + + [ConditionalFact] + public async Task OnStartedCalledForWebSocket() + { + var cws = new ClientWebSocket(); + await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketLifetimeEvents"), default); + + await ReceiveMessage(cws, "OnStarting"); + await ReceiveMessage(cws, "Upgraded"); + } + + [ConditionalFact] + public async Task WebReadBeforeUpgrade() + { + var cws = new ClientWebSocket(); + await cws.ConnectAsync(new Uri(_webSocketUri + "WebReadBeforeUpgrade"), default); + + await ReceiveMessage(cws, "Yay"); + } + + [ConditionalFact] + public async Task CanSendAndReceieveData() + { + var cws = new ClientWebSocket(); + await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketEcho"), default); + + for (int i = 0; i < 1000; i++) + { + var mesage = i.ToString(); + await SendMessage(cws, mesage); + await ReceiveMessage(cws, mesage); + } + } + + private async Task SendMessage(ClientWebSocket webSocket, string message) + { + await webSocket.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(message)), WebSocketMessageType.Text, true, default); + } + + private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMessage) + { + var received = new byte[expectedMessage.Length]; + + var offset = 0; + WebSocketReceiveResult result; + do + { + result = await webSocket.ReceiveAsync(new ArraySegment(received, offset, received.Length - offset), default); + offset += result.Count; + } while (!result.EndOfMessage); + + Assert.Equal(expectedMessage, Encoding.ASCII.GetString(received)); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/MultipleAppTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/MultipleAppTests.cs new file mode 100644 index 0000000000..349f72d7bf --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/MultipleAppTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class MultipleAppTests : IISFunctionalTestBase + { + private readonly PublishedSitesFixture _fixture; + + public MultipleAppTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalTheory] + [InlineData(AncmVersion.AspNetCoreModule)] + [InlineData(AncmVersion.AspNetCoreModuleV2)] + public Task Startup(AncmVersion ancmVersion) + { + // ANCM v1 currently does *not* retry if an app fails to start the first time due to a port collision. + // So, this test is expected to fail on v1 approximately 1 in 1,000 times (probably of at least one collision + // when 10 sites choose a random port from the range 1025-48000). Adding one retry should reduce the failure + // rate from 1 in 1,000 to 1 in 1,000,000. The previous product code (with "srand(GetTickCount())") should still + // fail the test reliably. + // https://github.com/aspnet/IISIntegration/issues/1350 + // + // ANCM v2 does retry on port collisions, so no retries should be required. + var attempts = (ancmVersion == AncmVersion.AspNetCoreModule) ? 2 : 1; + + return Helpers.Retry(async () => + { + const int numApps = 10; + + using (var deployers = new DisposableList()) + { + var deploymentResults = new List(); + + // Deploy all apps + for (var i = 0; i < numApps; i++) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel: IntegrationTesting.HostingModel.OutOfProcess); + deploymentParameters.AncmVersion = ancmVersion; + + var deployer = CreateDeployer(deploymentParameters); + deployers.Add(deployer); + deploymentResults.Add(await deployer.DeployAsync()); + } + + // Start all requests as quickly as possible, so apps are started as quickly as possible, + // to test possible race conditions when multiple apps start at the same time. + var requestTasks = new List>(); + foreach (var deploymentResult in deploymentResults) + { + requestTasks.Add(deploymentResult.HttpClient.GetAsync("/HelloWorld")); + } + + // Verify all apps started and return expected response + foreach (var requestTask in requestTasks) + { + var response = await requestTask; + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World", responseText); + } + } + }, + attempts: attempts, msDelay: 0); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs new file mode 100644 index 0000000000..3ff46bf304 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -0,0 +1,87 @@ +// 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.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class NtlmAuthenticationTests : IISFunctionalTestBase + { + // Test only runs on IISExpress today as our CI machines do not have + // Windows auth installed globally. + // TODO either enable windows auth on our CI or use containers to test this + // behavior + + private readonly PublishedSitesFixture _fixture; + + public NtlmAuthenticationTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22, Tfm.Net461) + .WithAllAncmVersions(); + + [ConditionalTheory] + [RequiresIIS(IISCapability.WindowsAuthentication)] + [MemberData(nameof(TestVariants))] + public async Task NtlmAuthentication(TestVariant variant) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:0/"; + + deploymentParameters.SetWindowsAuth(enabled: true); + + var result = await DeployAsync(deploymentParameters); + var response = await result.HttpClient.GetAsync("/HelloWorld"); + + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Hello World", responseText); + + var httpClient = result.HttpClient; + response = await httpClient.GetAsync("/Anonymous"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Anonymous?True", responseText); + + response = await httpClient.GetAsync("/Restricted"); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); + + response = await httpClient.GetAsync("/RestrictedNTLM"); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + // Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves. + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); + + response = await httpClient.GetAsync("/Forbidden"); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + + var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true }; + httpClient = result.CreateClient(httpClientHandler); + + response = await httpClient.GetAsync("/Anonymous"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Anonymous?True", responseText); + + response = await httpClient.GetAsync("/Restricted"); + responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotEmpty(responseText); + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/Properties/AssemblyInfo.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b26f48a815 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +[assembly: RequiresIIS] +[assembly: ShortClassName] diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs new file mode 100644 index 0000000000..2e0a68b9f7 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/RequiresIISAttribute.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class RequiresIISAttribute : Attribute, ITestCondition + { + public bool IsMet => IISExpressAncmSchema.SupportsInProcessHosting; + + public string SkipReason => IISExpressAncmSchema.SkipReason; + + public RequiresIISAttribute() { } + + public RequiresIISAttribute(IISCapability capabilities) + { + // IISCapabilities aren't pertinent to IISExpress + } + } +} diff --git a/src/IISIntegration/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs b/src/IISIntegration/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs new file mode 100644 index 0000000000..62d07c5fd9 --- /dev/null +++ b/src/IISIntegration/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs @@ -0,0 +1,83 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class UpgradeFeatureDetectionTests : IISFunctionalTestBase + { + private readonly string _isWebsocketsSupported = Environment.OSVersion.Version >= new Version(6, 2) ? "Enabled" : "Disabled"; + private readonly PublishedSitesFixture _fixture; + + public UpgradeFeatureDetectionTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + [ConditionalFact] + public Task UpgradeFeatureDetectionDisabled_InProcess() + { + // fails due to not modifying the apphost.config file. + return UpgradeFeatureDetectionDeployer( + disableWebSocket: true, + Helpers.GetInProcessTestSitesPath(), + "Disabled", HostingModel.InProcess); + } + + [ConditionalFact] + public Task UpgradeFeatureDetectionEnabled_InProcess() + { + return UpgradeFeatureDetectionDeployer( + disableWebSocket: false, + Helpers.GetInProcessTestSitesPath(), + _isWebsocketsSupported, HostingModel.InProcess); + } + + [ConditionalFact] + public Task UpgradeFeatureDetectionDisabled_OutOfProcess() + { + return UpgradeFeatureDetectionDeployer( + disableWebSocket: true, + Helpers.GetOutOfProcessTestSitesPath(), + "Disabled", HostingModel.OutOfProcess); + } + + [ConditionalFact] + public Task UpgradeFeatureDetectionEnabled_OutOfProcess() + { + return UpgradeFeatureDetectionDeployer( + disableWebSocket: false, + Helpers.GetOutOfProcessTestSitesPath(), + _isWebsocketsSupported, HostingModel.OutOfProcess); + } + + private async Task UpgradeFeatureDetectionDeployer(bool disableWebSocket, string sitePath, string expected, HostingModel hostingModel) + { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(hostingModel, publish: true); + + if (disableWebSocket) + { + // For IIS, we need to modify the apphost.config file + deploymentParameters.AddServerConfigAction( + element => element.Descendants("webSocket") + .Single() + .SetAttributeValue("enabled", "false")); + } + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("UpgradeFeatureDetection"); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, responseText); + } + } +} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Http.config b/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Http.config deleted file mode 100644 index 18a1aa2e22..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/Http.config +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/NtlmAuthentation.config b/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/NtlmAuthentation.config deleted file mode 100644 index d74f6b2e4f..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/NtlmAuthentation.config +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config b/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config deleted file mode 100644 index 541f91ad81..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config +++ /dev/null @@ -1,1029 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs deleted file mode 100644 index 370ce2cf63..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if NETCOREAPP2_0 || NETCOREAPP2_1 - -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - public class HelloWorldTests : LoggedTest - { - public HelloWorldTests(ITestOutputHelper output) : base(output) - { - } - - [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - [InlineData("V1")] - [InlineData("V2")] - public Task HelloWorld_IISExpress_Clr_X64_Portable(string ancmVersion) - { - return HelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable, ancmVersion); - } - - [Theory] - [InlineData("V1")] - [InlineData("V2")] - public Task HelloWorld_IISExpress_CoreClr_X64_Portable(string ancmVersion) - { - return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable, ancmVersion); - } - - private async Task HelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, string ancmVersion) - { - var serverType = ServerType.IISExpress; - var architecture = RuntimeArchitecture.x64; - var testName = $"HelloWorld_{runtimeFlavor}"; - using (StartLog(out var loggerFactory, testName)) - { - var logger = loggerFactory.CreateLogger("HelloWorldTest"); - - var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) - { - EnvironmentName = "HelloWorld", // Will pick the Start class named 'StartupHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, - SiteName = "HttpTestSite", // This is configured in the Http.config - TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", - ApplicationType = applicationType, - Configuration = -#if DEBUG - "Debug", -#else - "Release", -#endif - AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" - }; - - 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, retryCount: 30); - - var responseText = await response.Content.ReadAsStringAsync(); - try - { - Assert.Equal("Hello World", responseText); - - response = await deploymentResult.HttpClient.GetAsync("/Path%3F%3F?query"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal("/Path??", responseText); - - response = await deploymentResult.HttpClient.GetAsync("/Query%3FPath?query?"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal("?query?", responseText); - - response = await deploymentResult.HttpClient.GetAsync("/BodyLimit"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal("null", responseText); - - response = await deploymentResult.HttpClient.GetAsync("/Auth"); - responseText = await response.Content.ReadAsStringAsync(); - - // We adapted the Http.config file to be used for inprocess too. We specify WindowsAuth is enabled - // We now expect that windows auth is enabled rather than disabled. - Assert.True("backcompat;Windows".Equals(responseText) || "latest;Windows".Equals(responseText), "Auth"); - } - catch (XunitException) - { - logger.LogWarning(response.ToString()); - logger.LogWarning(responseText); - throw; - } - } - } - } - } -} -#elif NET461 -#else -#error Target frameworks need to be updated -#endif diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs deleted file mode 100644 index 0e71a21fdc..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if NETCOREAPP2_0 || NETCOREAPP2_1 - -using System; -using System.IO; -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - // IIS Express preregisteres 44300-44399 ports with SSL bindings. - // So these tests always have to use ports in this range, and we can't rely on OS-allocated ports without a whole lot of ceremony around - // creating self-signed certificates and registering SSL bindings with HTTP.sys - public class HttpsTest : LoggedTest - { - public HttpsTest(ITestOutputHelper output) : base(output) - { - } - - [Theory(Skip = "Full framework web.config generation is currently incorrect. See: https://github.com/aspnet/websdk/pull/322")] - [InlineData("V1")] - [InlineData("V2")] - public Task Https_HelloWorld_CLR_X64(string ancmVersion) - { - return HttpsHelloWorld(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44396, ancmVersion); - } - - [Theory] - [InlineData("V1")] - [InlineData("V2")] - public Task Https_HelloWorld_CoreCLR_X64_Portable(string ancmVersion) - { - return HttpsHelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44394, ancmVersion); - } - - private async Task HttpsHelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, string ancmVersion) - { - var serverType = ServerType.IISExpress; - var architecture = RuntimeArchitecture.x64; - - var applicationBaseUrl = $"https://localhost:{port}/"; - var testName = $"HttpsHelloWorld_{runtimeFlavor}"; - using (StartLog(out var loggerFactory, testName)) - { - var logger = loggerFactory.CreateLogger("HttpsHelloWorldTest"); - - var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) - { - ApplicationBaseUriHint = applicationBaseUrl, - EnvironmentName = "HttpsHelloWorld", // Will pick the Start class named 'StartupHttpsHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Https.config") : null, - SiteName = "HttpsTestSite", // This is configured in the Https.config - TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", - ApplicationType = applicationType, - Configuration = -#if DEBUG - "Debug", -#else - "Release", -#endif - AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" - - }; - - using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) - { - var deploymentResult = await deployer.DeployAsync(); - var handler = new HttpClientHandler(); - handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; - var httpClient = deploymentResult.CreateHttpClient(handler); - httpClient.Timeout = TimeSpan.FromSeconds(5); - - // 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, retryCount: 30); - - var responseText = await response.Content.ReadAsStringAsync(); - try - { - Assert.Equal("Scheme:https; Original:http", responseText); - } - catch (XunitException) - { - logger.LogWarning(response.ToString()); - logger.LogWarning(responseText); - throw; - } - } - } - } - - [Theory] - [InlineData("V1")] - [InlineData("V2")] - public Task Https_HelloWorld_NoClientCert_CoreCLR_X64_Portable(string ancmVersion) - { - return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable , port: 44397, sendClientCert: false, ancmVersion); - } - - [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - [InlineData("V1")] - [InlineData("V2")] - public Task Https_HelloWorld_NoClientCert_Clr_X64(string ancmVersion) - { - return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44398, sendClientCert: false, ancmVersion); - } - -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Theory(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] - [InlineData("V1")] - [InlineData("V2")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public Task Https_HelloWorld_ClientCert_Clr_X64(string ancmVersion) - { - return HttpsHelloWorldCerts(RuntimeFlavor.Clr, ApplicationType.Portable, port: 44301, sendClientCert: true, ancmVersion); - } - -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Theory(Skip = "Manual test only, selecting a client cert is non-determanistic on different machines.")] - [InlineData("V1")] - [InlineData("V2")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public Task Https_HelloWorld_ClientCert_CoreCLR_X64_Portable(string ancmVersion) - { - return HttpsHelloWorldCerts(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 44302, sendClientCert: true, ancmVersion); - } - - private async Task HttpsHelloWorldCerts(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, bool sendClientCert, string ancmVersion) - { - var serverType = ServerType.IISExpress; - var architecture = RuntimeArchitecture.x64; - var applicationBaseUrl = $"https://localhost:{port}/"; - var testName = $"HttpsHelloWorldCerts_{runtimeFlavor}"; - using (StartLog(out var loggerFactory, testName)) - { - var logger = loggerFactory.CreateLogger("HttpsHelloWorldTest"); - - var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) - { - ApplicationBaseUriHint = applicationBaseUrl, - EnvironmentName = "HttpsHelloWorld", // Will pick the Start class named 'StartupHttpsHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Https.config") : null, - SiteName = "HttpsTestSite", // This is configured in the Https.config - TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", - ApplicationType = applicationType, - Configuration = -#if DEBUG - "Debug", -#else - "Release", -#endif - AdditionalPublishParameters = $" /p:ANCMVersion={ancmVersion}" - }; - - using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) - { - var deploymentResult = await deployer.DeployAsync(); - var handler = new HttpClientHandler(); - handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - if (sendClientCert) - { - X509Certificate2 clientCert = FindClientCert(); - Assert.NotNull(clientCert); - handler.ClientCertificates.Add(clientCert); - } - var httpClient = deploymentResult.CreateHttpClient(handler); - - // 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("checkclientcert"); - }, logger, deploymentResult.HostShutdownToken); - - var responseText = await response.Content.ReadAsStringAsync(); - try - { - if (sendClientCert) - { - Assert.Equal("Scheme:https; Original:http; has cert? True", responseText); - } - else - { - Assert.Equal("Scheme:https; Original:http; has cert? False", responseText); - } - } - catch (XunitException) - { - logger.LogWarning(response.ToString()); - logger.LogWarning(responseText); - throw; - } - } - } - } - - private X509Certificate2 FindClientCert() - { - var store = new X509Store(); - store.Open(OpenFlags.ReadOnly); - - foreach (var cert in store.Certificates) - { - bool isClientAuth = false; - bool isSmartCard = false; - foreach (var extension in cert.Extensions) - { - var eku = extension as X509EnhancedKeyUsageExtension; - if (eku != null) - { - foreach (var oid in eku.EnhancedKeyUsages) - { - if (oid.FriendlyName == "Client Authentication") - { - isClientAuth = true; - } - else if (oid.FriendlyName == "Smart Card Logon") - { - isSmartCard = true; - break; - } - } - } - } - - if (isClientAuth && !isSmartCard) - { - return cert; - } - } - return null; - } - } -} -#elif NET461 -#else -#error Target frameworks need to be updated -#endif diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs deleted file mode 100644 index 809c213b27..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if NET461 -// Per https://github.com/dotnet/corefx/issues/5045, HttpClientHandler.UseDefaultCredentials does not work correctly in CoreFx. -// We'll require the desktop HttpClient to run these tests. - -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - public class NtlmAuthenticationTests : LoggedTest - { - public NtlmAuthenticationTests(ITestOutputHelper output) : base(output) - { - } - - [Theory(Skip = "Full framework web.config generation is currently incorrect. See https://github.com/aspnet/websdk/pull/322")] - [InlineData("V1")] - [InlineData("V2")] - public Task NtlmAuthentication_Clr_X64(string ancmVersion) - { - return NtlmAuthentication(RuntimeFlavor.Clr, ApplicationType.Portable, port: 5051, ancmVersion); - } - - [Theory] - [InlineData("V1")] - [InlineData("V2")] - public Task NtlmAuthentication_CoreClr_X64_Portable(string ancmVersion) - { - return NtlmAuthentication(RuntimeFlavor.CoreClr, ApplicationType.Portable, port: 5052, ancmVersion); - } - - private async Task NtlmAuthentication(RuntimeFlavor runtimeFlavor, ApplicationType applicationType, int port, string ancmVersion) - { - var serverType = ServerType.IISExpress; - var architecture = RuntimeArchitecture.x64; - var testName = $"NtlmAuthentication_{runtimeFlavor}"; - using (StartLog(out var loggerFactory, testName)) - { - var logger = loggerFactory.CreateLogger("NtlmAuthenticationTest"); - - var windowsRid = architecture == RuntimeArchitecture.x64 - ? "win7-x64" - : "win7-x86"; - var additionalPublishParameters = $" /p:ANCMVersion={ancmVersion}"; - if (ApplicationType.Standalone == applicationType && RuntimeFlavor.CoreClr == runtimeFlavor) - { - additionalPublishParameters += " -r " + windowsRid; - - } - - var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) - { - ApplicationBaseUriHint = $"http://localhost:{port}", - EnvironmentName = "NtlmAuthentication", // Will pick the Start class named 'StartupNtlmAuthentication' - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/NtlmAuthentation.config") : null, - SiteName = "NtlmAuthenticationTestSite", // This is configured in the NtlmAuthentication.config - TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", - ApplicationType = applicationType, - AdditionalPublishParameters = additionalPublishParameters, - Configuration = -#if DEBUG - "Debug" -#else - "Release" -#endif - }; - - using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) - { - var deploymentResult = await deployer.DeployAsync(); - var httpClient = deploymentResult.HttpClient; - httpClient.Timeout = TimeSpan.FromSeconds(5); - - // 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, retryCount: 30); - - var responseText = await response.Content.ReadAsStringAsync(); - try - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Hello World", responseText); - - response = await httpClient.GetAsync("/Anonymous"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Anonymous?True", responseText); - - 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()); - - response = await httpClient.GetAsync("/RestrictedNTLM"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); - // Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves. - Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); - - response = await httpClient.GetAsync("/Forbidden"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - - var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true }; - httpClient = deploymentResult.CreateHttpClient(httpClientHandler); - - response = await httpClient.GetAsync("/Anonymous"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Anonymous?True", responseText); - - response = await httpClient.GetAsync("/Restricted"); - responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.NotEmpty(responseText); - } - catch (XunitException) - { - logger.LogWarning(response.ToString()); - logger.LogWarning(responseText); - throw; - } - } - } - } - } -} -#elif NETCOREAPP2_0 || NETCOREAPP2_1 -#else -#error Target frameworks need to be updated -#endif diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs deleted file mode 100644 index 8627334de7..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - public class UpgradeFeatureDetectionTests : LoggedTest - { - private string _isWebsocketsSupported = Environment.OSVersion.Version >= new Version(6, 2) ? "Enabled" : "Disabled"; - - public UpgradeFeatureDetectionTests(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public Task UpgradeFeatureDetectionEnabled_OutOfProcess_IISExpress_CoreClr_x64_Portable() - { - return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, - ApplicationType.Portable, - "AppHostConfig/WebsocketsNotSupported.config", - Helpers.GetOutOfProcessTestSitesPath(), - "Disabled"); - } - - [Fact] - public Task UpgradeFeatureDetectionDisabled_OutOfProcess_IISExpress_CoreClr_x64_Portable() - { - return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, - ApplicationType.Portable, - "AppHostConfig/Http.config", - Helpers.GetOutOfProcessTestSitesPath(), - _isWebsocketsSupported); - } - - private async Task UpgradeFeatureDetectionDeployer(RuntimeFlavor runtimeFlavor, - ApplicationType applicationType, - string configPath, - string sitePath, - string expected) - { - var serverType = ServerType.IISExpress; - var architecture = RuntimeArchitecture.x64; - var testName = $"HelloWorld_{runtimeFlavor}"; - using (StartLog(out var loggerFactory, testName)) - { - var logger = loggerFactory.CreateLogger("HelloWorldTest"); - - var deploymentParameters = new DeploymentParameters(sitePath, serverType, runtimeFlavor, architecture) - { - EnvironmentName = "UpgradeFeatureDetection", // Will pick the Start class named 'StartupHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(configPath) : null, - SiteName = "HttpTestSite", // This is configured in the Http.config - TargetFramework = "netcoreapp2.1", - ApplicationType = applicationType, - Configuration = -#if DEBUG - "Debug" -#else - "Release" -#endif - }; - - using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) - { - var deploymentResult = await deployer.DeployAsync(); - deploymentResult.HttpClient.Timeout = TimeSpan.FromSeconds(5); - - // 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("UpgradeFeatureDetection"); - }, logger, deploymentResult.HostShutdownToken, retryCount: 30); - - var responseText = await response.Content.ReadAsStringAsync(); - try - { - Assert.Equal(expected, responseText); - } - catch (XunitException) - { - logger.LogWarning(response.ToString()); - logger.LogWarning(responseText); - throw; - } - } - } - } - } -} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs deleted file mode 100644 index 5cda360005..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Server.IntegrationTesting; -using System; -using System.IO; -using System.Linq; -using System.Xml.Linq; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - public class Helpers - { - public static string GetTestWebSitePath(string name) - { - return Path.GetFullPath( - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, - "..", // tfm - "..", // debug - "..", // obj - "..", // projectfolder - "WebSites", - name)); - } - - public static string GetInProcessTestSitesPath() => GetTestWebSitePath("InProcessWebSite"); - - public static string GetOutOfProcessTestSitesPath() => GetTestWebSitePath("OutOfProcessWebSite"); - - public static void ModifyAspNetCoreSectionInWebConfig(DeploymentResult deploymentResult, string key, string value) - { - // modify the web.config after publish - var root = deploymentResult.ContentRoot; - var webConfigFile = $"{root}/web.config"; - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - } -} diff --git a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs b/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs deleted file mode 100644 index 5f2edd22f6..0000000000 --- a/src/IISIntegration/test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Xml.Linq; -using Microsoft.AspNetCore.Testing.xunit; - -namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests -{ - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Assembly | AttributeTargets.Class)] - public sealed class IISExpressSupportsInProcessHostingAttribute : Attribute, ITestCondition - { - public bool IsMet => AncmSchema.SupportsInProcessHosting; - - public string SkipReason => AncmSchema.SkipReason; - - private class AncmSchema - { - public static bool SupportsInProcessHosting { get; } - public static string SkipReason { get; } = "IIS Express must be upgraded to support in-process hosting."; - - static AncmSchema() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - SkipReason = "IIS Express tests can only be run on Windows"; - return; - } - - var ancmConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - "IIS Express", "config", "schema", "aspnetcore_schema.xml"); - - if (!File.Exists(ancmConfigPath)) - { - SkipReason = "IIS Express is not installed."; - return; - } - - XDocument ancmConfig; - - try - { - ancmConfig = XDocument.Load(ancmConfigPath); - } - catch - { - SkipReason = "Could not read ANCM schema configuration"; - return; - } - - SupportsInProcessHosting = ancmConfig - .Root - .Descendants("attribute") - .Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal)); - } - } - } -} diff --git a/src/IISIntegration/test/TestTasks/InjectRequestHandler.cs b/src/IISIntegration/test/TestTasks/InjectRequestHandler.cs index 639428ebd1..4da8129d71 100644 --- a/src/IISIntegration/test/TestTasks/InjectRequestHandler.cs +++ b/src/IISIntegration/test/TestTasks/InjectRequestHandler.cs @@ -14,9 +14,9 @@ namespace TestTasks { private static void Main(string[] args) { - var depsFile = args[2]; - var rid = args[0]; - var libraryLocation = args[1]; + string rid = args[0]; + string libraryLocation = args[1]; + string depsFile = args[2]; JToken deps; using (var file = File.OpenText(depsFile)) diff --git a/src/IISIntegration/test/WebSites/Directory.Build.props b/src/IISIntegration/test/WebSites/Directory.Build.props new file mode 100644 index 0000000000..9b29d34f16 --- /dev/null +++ b/src/IISIntegration/test/WebSites/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/InProcessWebSite.csproj b/src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/InProcessWebSite.csproj new file mode 100644 index 0000000000..4d0952108a --- /dev/null +++ b/src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/InProcessWebSite.csproj @@ -0,0 +1,32 @@ + + + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json b/src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/Properties/launchSettings.json similarity index 61% rename from src/IISIntegration/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json rename to src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/Properties/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/Properties/launchSettings.json +++ b/src/IISIntegration/test/WebSites/InProcessForwardsCompatWebSite/Properties/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/Program.cs b/src/IISIntegration/test/WebSites/InProcessWebSite/DummyServer.cs similarity index 53% rename from src/IISIntegration/test/WebSites/OverriddenServerWebSite/Program.cs rename to src/IISIntegration/test/WebSites/InProcessWebSite/DummyServer.cs index bb65e03004..d122f05776 100644 --- a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/Program.cs +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/DummyServer.cs @@ -4,30 +4,12 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.DependencyInjection; -namespace IISTestSite +namespace TestSite { - public static class Program - { - public static void Main(string[] args) - { - var host = new WebHostBuilder() - .UseIISIntegration() - .ConfigureServices(services => services.AddSingleton()) - .Configure(builder => builder.Run(async context => { await context.Response.WriteAsync("I shouldn't work"); })) - .Build(); - - host.Run(); - } - } - - public class DummyServer: IServer + public class DummyServer : IServer { public void Dispose() { diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/InProcessWebSite.csproj b/src/IISIntegration/test/WebSites/InProcessWebSite/InProcessWebSite.csproj index c615d460ee..d007d2daa1 100644 --- a/src/IISIntegration/test/WebSites/InProcessWebSite/InProcessWebSite.csproj +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/InProcessWebSite.csproj @@ -1,20 +1,32 @@  + - $(StandardTestTfms) + netcoreapp2.2 + true - + - + + + + + + + + + + + diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/Program.cs b/src/IISIntegration/test/WebSites/InProcessWebSite/Program.cs index 0550f0f1fd..c40448ee91 100644 --- a/src/IISIntegration/test/WebSites/InProcessWebSite/Program.cs +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/Program.cs @@ -1,14 +1,101 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace IISTestSite +namespace TestSite { public static class Program { - public static void Main(string[] args) + public static int Main(string[] args) + { + var mode = args.FirstOrDefault(); + switch (mode) + { + case "CreateFile": + File.WriteAllText(args[1], ""); + return StartServer(); + case "CheckLargeStdOutWrites": + Console.WriteLine(new string('a', 30000)); + break; + case "CheckLargeStdErrWrites": + Console.Error.WriteLine(new string('a', 30000)); + Console.Error.Flush(); + break; + case "ConsoleWrite": + Console.WriteLine($"Random number: {args[1]}"); + break; + case "ConsoleErrorWrite": + Console.Error.WriteLine($"Random number: {args[1]}"); + Console.Error.Flush(); + break; + case "CheckOversizedStdErrWrites": + Console.WriteLine(new string('a', 31000)); + break; + case "CheckOversizedStdOutWrites": + Console.Error.WriteLine(new string('a', 31000)); + Console.Error.Flush(); + break; + case "Hang": + Thread.Sleep(Timeout.Infinite); + break; + case "Throw": + throw new InvalidOperationException("Program.Main exception"); + case "EarlyReturn": + return 12; + case "HangOnStop": + { + var host = new WebHostBuilder() + .UseIIS() + .UseStartup() + .Build(); + host.Run(); + + Thread.Sleep(Timeout.Infinite); + } + break; + case "CheckConsoleFunctions": + // Call a bunch of console functions and make sure none return invalid handle. + Console.OutputEncoding = Encoding.UTF8; + Console.Title = "Test"; + Console.WriteLine($"Is Console redirection: {Console.IsOutputRedirected}"); + Console.BackgroundColor = ConsoleColor.Blue; + Console.WriteLine("彡⾔"); + break; + case "OverriddenServer": + { + var host = new WebHostBuilder() + .UseIIS() + .ConfigureServices(services => services.AddSingleton()) + .Configure(builder => builder.Run(async context => { await context.Response.WriteAsync("I shouldn't work"); })) + .Build(); + host.Run(); + } + break; + case "ConsoleErrorWriteStartServer": + Console.Error.WriteLine("TEST MESSAGE"); + return StartServer(); + case "ConsoleWriteStartServer": + Console.WriteLine("TEST MESSAGE"); + return StartServer(); + default: + return StartServer(); + + } + return 12; + } + + private static int StartServer() { var host = new WebHostBuilder() .ConfigureLogging((_, factory) => @@ -16,11 +103,12 @@ namespace IISTestSite factory.AddConsole(); factory.AddFilter("Console", level => level >= LogLevel.Information); }) - .UseIISIntegration() - .UseStartup(typeof(Program).Assembly.FullName) + .UseIIS() + .UseStartup() .Build(); host.Run(); + return 0; } } } diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/Properties/launchSettings.json b/src/IISIntegration/test/WebSites/InProcessWebSite/Properties/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/test/WebSites/InProcessWebSite/Properties/launchSettings.json +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/Properties/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.WebSockets.cs b/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.WebSockets.cs new file mode 100644 index 0000000000..d658ebd03f --- /dev/null +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.WebSockets.cs @@ -0,0 +1,147 @@ +// 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.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace TestSite +{ + public partial class Startup + { + + private void WebsocketRequest(IApplicationBuilder app) + { + app.Run(async context => + { + await context.Response.WriteAsync("test"); + }); + } + + private void WebReadBeforeUpgrade(IApplicationBuilder app) + { + app.Run(async context => { + + var singleByteArray = new byte[1]; + Assert.Equal(0, await context.Request.Body.ReadAsync(singleByteArray, 0, 1)); + + var ws = await Upgrade(context); + await SendMessages(ws, "Yay"); + }); + } + + private void WebSocketEcho(IApplicationBuilder app) + { + app.Run(async context => + { + var ws = await Upgrade(context); + + var appLifetime = app.ApplicationServices.GetRequiredService(); + + await Echo(ws, appLifetime.ApplicationStopping); + }); + } + + private void WebSocketLifetimeEvents(IApplicationBuilder app) + { + app.Run(async context => { + + var messages = new List(); + + context.Response.OnStarting(() => { + context.Response.Headers["custom-header"] = "value"; + messages.Add("OnStarting"); + return Task.CompletedTask; + }); + + var ws = await Upgrade(context); + messages.Add("Upgraded"); + + await SendMessages(ws, messages.ToArray()); + }); + } + + private static async Task SendMessages(WebSocket webSocket, params string[] messages) + { + foreach (var message in messages) + { + await webSocket.SendAsync(new ArraySegment(Encoding.ASCII.GetBytes(message)), WebSocketMessageType.Text, true, CancellationToken.None); + } + } + + private static async Task Upgrade(HttpContext context) + { + var upgradeFeature = context.Features.Get(); + + // Generate WebSocket response headers + string key = context.Request.Headers[Constants.Headers.SecWebSocketKey].ToString(); + var responseHeaders = HandshakeHelpers.GenerateResponseHeaders(key); + foreach (var headerPair in responseHeaders) + { + context.Response.Headers[headerPair.Key] = headerPair.Value; + } + + // Upgrade the connection + Stream opaqueTransport = await upgradeFeature.UpgradeAsync(); + + // Get the WebSocket object + var ws = WebSocketProtocol.CreateFromStream(opaqueTransport, isServer: true, subProtocol: null, keepAliveInterval: TimeSpan.FromMinutes(2)); + return ws; + } + + private async Task Echo(WebSocket webSocket, CancellationToken token) + { + var buffer = new byte[1024 * 4]; + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token); + bool closeFromServer = false; + string closeFromServerCmd = "CloseFromServer"; + int closeFromServerLength = closeFromServerCmd.Length; + + while (!result.CloseStatus.HasValue && !token.IsCancellationRequested && !closeFromServer) + { + if (result.Count == closeFromServerLength && + Encoding.ASCII.GetString(buffer).Substring(0, result.Count) == closeFromServerCmd) + { + // The client sent "CloseFromServer" text message to request the server to close (a test scenario). + closeFromServer = true; + } + else + { + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, token); + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token); + } + } + + if (result.CloseStatus.HasValue) + { + // Client-initiated close handshake + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + else + { + // Server-initiated close handshake due to either of the two conditions: + // (1) The applicaton host is performing a graceful shutdown. + // (2) The client sent "CloseFromServer" text message to request the server to close (a test scenario). + await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeFromServerCmd, CancellationToken.None); + + // The server has sent the Close frame. + // Stop sending but keep receiving until we get the Close frame from the client. + while (!result.CloseStatus.HasValue) + { + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + } + } + } +} diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.cs b/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.cs index 23cec82f79..eb84272b76 100644 --- a/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.cs +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/Startup.cs @@ -2,683 +2,698 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; -using System.Reflection; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.Server.IIS; -using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using Xunit; -namespace IISTestSite +namespace TestSite { - public class Startup + public partial class Startup { public void Configure(IApplicationBuilder app) { - foreach (var method in GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + TestStartup.Register(app, this); + } + + private async Task ServerVariable(HttpContext ctx) + { + var varName = ctx.Request.Query["q"]; + var newValue = ctx.Request.Query["v"]; + var feature = ctx.Features.Get(); + if (newValue.Count != 0) { - var parameters = method.GetParameters(); - if (method.Name != nameof(Configure) && - parameters.Length == 1 && - parameters[0].ParameterType == typeof(IApplicationBuilder)) - { - app.Map("/" + method.Name, innerAppBuilder => method.Invoke(this, new[] { innerAppBuilder })); - } + feature[varName] = newValue; + } + await ctx.Response.WriteAsync($"{varName}: {feature[varName] ?? "(null)"}"); + } + + private async Task AuthenticationAnonymous(HttpContext ctx) + { + await ctx.Response.WriteAsync("Anonymous?" + !ctx.User.Identity.IsAuthenticated); + } + + private async Task AuthenticationRestricted(HttpContext ctx) + { + if (ctx.User.Identity.IsAuthenticated) + { + await ctx.Response.WriteAsync(ctx.User.Identity.AuthenticationType); + } + else + { + await ctx.ChallengeAsync(IISServerDefaults.AuthenticationScheme); } } - private void ServerVariable(IApplicationBuilder app) + private async Task AuthenticationForbidden(HttpContext ctx) { - app.Run(async ctx => - { - var varName = ctx.Request.Query["q"]; - await ctx.Response.WriteAsync($"{varName}: {ctx.GetIISServerVariable(varName) ?? "(null)"}"); - }); + await ctx.ForbidAsync(IISServerDefaults.AuthenticationScheme); } - public void AuthenticationAnonymous(IApplicationBuilder app) + private async Task AuthenticationRestrictedNTLM(HttpContext ctx) { - app.Run(async ctx => + if (string.Equals("NTLM", ctx.User.Identity.AuthenticationType, StringComparison.Ordinal)) { - await ctx.Response.WriteAsync("Anonymous?" + !ctx.User.Identity.IsAuthenticated); - }); + await ctx.Response.WriteAsync("NTLM"); + } + else + { + await ctx.ChallengeAsync(IISServerDefaults.AuthenticationScheme); + } } - private void AuthenticationRestricted(IApplicationBuilder app) + private async Task FeatureCollectionSetRequestFeatures(HttpContext ctx) { - app.Run(async ctx => + try { - if (ctx.User.Identity.IsAuthenticated) - { - await ctx.Response.WriteAsync(ctx.User.Identity.AuthenticationType); - } - else - { - await ctx.ChallengeAsync(IISDefaults.AuthenticationScheme); - } - }); + Assert.Equal("GET", ctx.Request.Method); + ctx.Request.Method = "test"; + Assert.Equal("test", ctx.Request.Method); + + Assert.Equal("http", ctx.Request.Scheme); + ctx.Request.Scheme = "test"; + Assert.Equal("test", ctx.Request.Scheme); + + Assert.Equal("/FeatureCollectionSetRequestFeatures", ctx.Request.PathBase); + ctx.Request.PathBase = "/base"; + Assert.Equal("/base", ctx.Request.PathBase); + + Assert.Equal("/path", ctx.Request.Path); + ctx.Request.Path = "/path"; + Assert.Equal("/path", ctx.Request.Path); + + Assert.Equal("?query", ctx.Request.QueryString.Value); + ctx.Request.QueryString = QueryString.Empty; + Assert.Equal("", ctx.Request.QueryString.Value); + + Assert.Equal("HTTP/1.1", ctx.Request.Protocol); + ctx.Request.Protocol = "HTTP/1.0"; + Assert.Equal("HTTP/1.0", ctx.Request.Protocol); + + Assert.NotNull(ctx.Request.Headers); + var headers = new HeaderDictionary(); + ctx.Features.Get().Headers = headers; + Assert.Same(headers, ctx.Features.Get().Headers); + + Assert.NotNull(ctx.Request.Body); + var body = new MemoryStream(); + ctx.Request.Body = body; + Assert.Same(body, ctx.Request.Body); + + //Assert.NotNull(ctx.Features.Get().TraceIdentifier); + //Assert.NotEqual(CancellationToken.None, ctx.RequestAborted); + //var token = new CancellationTokenSource().Token; + //ctx.RequestAborted = token; + //Assert.Equal(token, ctx.RequestAborted); + + await ctx.Response.WriteAsync("Success"); + return; + } + catch (Exception exception) + { + ctx.Response.StatusCode = 500; + await ctx.Response.WriteAsync(exception.ToString()); + } + await ctx.Response.WriteAsync("_Failure"); } - public void AuthenticationForbidden(IApplicationBuilder app) + private async Task FeatureCollectionSetResponseFeatures(HttpContext ctx) { - app.Run(async ctx => + try { - await ctx.ForbidAsync(IISDefaults.AuthenticationScheme); - }); + Assert.Equal(200, ctx.Response.StatusCode); + ctx.Response.StatusCode = 404; + Assert.Equal(404, ctx.Response.StatusCode); + ctx.Response.StatusCode = 200; + + Assert.Null(ctx.Features.Get().ReasonPhrase); + ctx.Features.Get().ReasonPhrase = "Set Response"; + Assert.Equal("Set Response", ctx.Features.Get().ReasonPhrase); + + Assert.NotNull(ctx.Response.Headers); + var headers = new HeaderDictionary(); + ctx.Features.Get().Headers = headers; + Assert.Same(headers, ctx.Features.Get().Headers); + + var originalBody = ctx.Response.Body; + Assert.NotNull(originalBody); + var body = new MemoryStream(); + ctx.Response.Body = body; + Assert.Same(body, ctx.Response.Body); + ctx.Response.Body = originalBody; + + await ctx.Response.WriteAsync("Success"); + return; + } + catch (Exception exception) + { + ctx.Response.StatusCode = 500; + await ctx.Response.WriteAsync(exception.ToString()); + } + await ctx.Response.WriteAsync("_Failure"); } - public void AuthenticationRestrictedNTLM(IApplicationBuilder app) + private async Task FeatureCollectionSetConnectionFeatures(HttpContext ctx) { - app.Run(async ctx => + try { - if (string.Equals("NTLM", ctx.User.Identity.AuthenticationType, StringComparison.Ordinal)) - { - await ctx.Response.WriteAsync("NTLM"); - } - else - { - await ctx.ChallengeAsync(IISDefaults.AuthenticationScheme); - } - }); + Assert.True(IPAddress.IsLoopback(ctx.Connection.LocalIpAddress)); + ctx.Connection.LocalIpAddress = IPAddress.IPv6Any; + Assert.Equal(IPAddress.IPv6Any, ctx.Connection.LocalIpAddress); + + Assert.True(IPAddress.IsLoopback(ctx.Connection.RemoteIpAddress)); + ctx.Connection.RemoteIpAddress = IPAddress.IPv6Any; + Assert.Equal(IPAddress.IPv6Any, ctx.Connection.RemoteIpAddress); + await ctx.Response.WriteAsync("Success"); + return; + } + catch (Exception exception) + { + ctx.Response.StatusCode = 500; + await ctx.Response.WriteAsync(exception.ToString()); + } + await ctx.Response.WriteAsync("_Failure"); } - private void FeatureCollectionSetRequestFeatures(IApplicationBuilder app) + private void Throw(HttpContext ctx) { - app.Run(async context => + throw new Exception(); + } + + private async Task SetCustomErorCode(HttpContext ctx) + { + var feature = ctx.Features.Get(); + feature.ReasonPhrase = ctx.Request.Query["reason"]; + feature.StatusCode = int.Parse(ctx.Request.Query["code"]); + if (ctx.Request.Query["writeBody"] == "True") { + await ctx.Response.WriteAsync(ctx.Request.Query["body"]); + } + } + + private async Task HelloWorld(HttpContext ctx) + { + if (ctx.Request.Path.Value.StartsWith("/Path")) + { + await ctx.Response.WriteAsync(ctx.Request.Path.Value); + return; + } + if (ctx.Request.Path.Value.StartsWith("/Query")) + { + await ctx.Response.WriteAsync(ctx.Request.QueryString.Value); + return; + } + + await ctx.Response.WriteAsync("Hello World"); + } + + private async Task LargeResponseBody(HttpContext ctx) + { + if (int.TryParse(ctx.Request.Query["length"], out var length)) + { + await ctx.Response.WriteAsync(new string('a', length)); + } + } + + private async Task ResponseHeaders(HttpContext ctx) + { + ctx.Response.Headers["UnknownHeader"] = "test123=foo"; + ctx.Response.ContentType = "text/plain"; + ctx.Response.Headers["MultiHeader"] = new StringValues(new string[] { "1", "2" }); + await ctx.Response.WriteAsync("Request Complete"); + } + + private async Task ResponseInvalidOrdering(HttpContext ctx) + { + if (ctx.Request.Path.Equals("/SetStatusCodeAfterWrite")) + { + await ctx.Response.WriteAsync("Started_"); try { - Assert.Equal("GET", context.Request.Method); - context.Request.Method = "test"; - Assert.Equal("test", context.Request.Method); - - Assert.Equal("http", context.Request.Scheme); - context.Request.Scheme = "test"; - Assert.Equal("test", context.Request.Scheme); - - Assert.Equal("/FeatureCollectionSetRequestFeatures", context.Request.PathBase); - context.Request.PathBase = "/base"; - Assert.Equal("/base", context.Request.PathBase); - - Assert.Equal("/path", context.Request.Path); - context.Request.Path = "/path"; - Assert.Equal("/path", context.Request.Path); - - Assert.Equal("?query", context.Request.QueryString.Value); - context.Request.QueryString = QueryString.Empty; - Assert.Equal("", context.Request.QueryString.Value); - - Assert.Equal("HTTP/1.1", context.Request.Protocol); - context.Request.Protocol = "HTTP/1.0"; - Assert.Equal("HTTP/1.0", context.Request.Protocol); - - Assert.NotNull(context.Request.Headers); - var headers = new HeaderDictionary(); - context.Features.Get().Headers = headers; - Assert.Same(headers, context.Features.Get().Headers); - - Assert.NotNull(context.Request.Body); - var body = new MemoryStream(); - context.Request.Body = body; - Assert.Same(body, context.Request.Body); - - //Assert.NotNull(context.Features.Get().TraceIdentifier); - //Assert.NotEqual(CancellationToken.None, context.RequestAborted); - //var token = new CancellationTokenSource().Token; - //context.RequestAborted = token; - //Assert.Equal(token, context.RequestAborted); - - await context.Response.WriteAsync("Success"); - return; + ctx.Response.StatusCode = 200; } - catch (Exception exception) + catch (InvalidOperationException) { - context.Response.StatusCode = 500; - await context.Response.WriteAsync(exception.ToString()); + await ctx.Response.WriteAsync("SetStatusCodeAfterWriteThrew_"); } - await context.Response.WriteAsync("_Failure"); - }); - } - - private void FeatureCollectionSetResponseFeatures(IApplicationBuilder app) - { - app.Run(async context => + await ctx.Response.WriteAsync("Finished"); + return; + } + else if (ctx.Request.Path.Equals("/SetHeaderAfterWrite")) { + await ctx.Response.WriteAsync("Started_"); try { - Assert.Equal(200, context.Response.StatusCode); - context.Response.StatusCode = 404; - Assert.Equal(404, context.Response.StatusCode); - context.Response.StatusCode = 200; - - Assert.Null(context.Features.Get().ReasonPhrase); - context.Features.Get().ReasonPhrase = "Set Response"; - Assert.Equal("Set Response", context.Features.Get().ReasonPhrase); - - Assert.NotNull(context.Response.Headers); - var headers = new HeaderDictionary(); - context.Features.Get().Headers = headers; - Assert.Same(headers, context.Features.Get().Headers); - - var originalBody = context.Response.Body; - Assert.NotNull(originalBody); - var body = new MemoryStream(); - context.Response.Body = body; - Assert.Same(body, context.Response.Body); - context.Response.Body = originalBody; - - await context.Response.WriteAsync("Success"); - return; + ctx.Response.Headers["This will fail"] = "some value"; } - catch (Exception exception) + catch (InvalidOperationException) { - context.Response.StatusCode = 500; - await context.Response.WriteAsync(exception.ToString()); + await ctx.Response.WriteAsync("SetHeaderAfterWriteThrew_"); } - await context.Response.WriteAsync("_Failure"); - }); + await ctx.Response.WriteAsync("Finished"); + return; + } } - private void FeatureCollectionSetConnectionFeatures(IApplicationBuilder app) + private async Task CheckEnvironmentVariable(HttpContext ctx) { - app.Run(async context => - { - try - { - Assert.True(IPAddress.IsLoopback(context.Connection.LocalIpAddress)); - context.Connection.LocalIpAddress = IPAddress.IPv6Any; - Assert.Equal(IPAddress.IPv6Any, context.Connection.LocalIpAddress); - - Assert.True(IPAddress.IsLoopback(context.Connection.RemoteIpAddress)); - context.Connection.RemoteIpAddress = IPAddress.IPv6Any; - Assert.Equal(IPAddress.IPv6Any, context.Connection.RemoteIpAddress); - await context.Response.WriteAsync("Success"); - return; - } - catch (Exception exception) - { - context.Response.StatusCode = 500; - await context.Response.WriteAsync(exception.ToString()); - } - await context.Response.WriteAsync("_Failure"); - }); + var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_VALUE"); + await ctx.Response.WriteAsync(variable); } - private void Throw(IApplicationBuilder app) + private async Task CheckEnvironmentLongValueVariable(HttpContext ctx) { - app.Run(ctx => { throw new Exception(); }); + var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_LONG_VALUE"); + await ctx.Response.WriteAsync(variable); } - private void SetCustomErorCode(IApplicationBuilder app) + private async Task CheckAppendedEnvironmentVariable(HttpContext ctx) { - app.Run(async ctx => { - var feature = ctx.Features.Get(); - feature.ReasonPhrase = ctx.Request.Query["reason"]; - feature.StatusCode = int.Parse(ctx.Request.Query["code"]); - await ctx.Response.WriteAsync("Body"); - }); + var variable = Environment.GetEnvironmentVariable("ProgramFiles"); + await ctx.Response.WriteAsync(variable); } - private void HelloWorld(IApplicationBuilder app) + private async Task CheckRemoveAuthEnvironmentVariable(HttpContext ctx) { - app.Run(async ctx => - { - if (ctx.Request.Path.Value.StartsWith("/Path")) - { - await ctx.Response.WriteAsync(ctx.Request.Path.Value); - return; - } - if (ctx.Request.Path.Value.StartsWith("/Query")) - { - await ctx.Response.WriteAsync(ctx.Request.QueryString.Value); - return; - } - - await ctx.Response.WriteAsync("Hello World"); - }); + var variable = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_HTTPAUTH"); + await ctx.Response.WriteAsync(variable); } - - private void LargeResponseBody(IApplicationBuilder app) + private async Task ReadAndWriteSynchronously(HttpContext ctx) { - app.Run(async context => - { - if (int.TryParse(context.Request.Query["length"], out var length)) - { - await context.Response.WriteAsync(new string('a', length)); - } - }); + var t2 = Task.Run(() => WriteManyTimesToResponseBody(ctx)); + var t1 = Task.Run(() => ReadRequestBody(ctx)); + await Task.WhenAll(t1, t2); } - private void ResponseHeaders(IApplicationBuilder app) - { - app.Run(async context => - { - context.Response.Headers["UnknownHeader"] = "test123=foo"; - context.Response.ContentType = "text/plain"; - context.Response.Headers["MultiHeader"] = new StringValues(new string[] { "1", "2" }); - await context.Response.WriteAsync("Request Complete"); - }); - } - - private void ResponseInvalidOrdering(IApplicationBuilder app) - { - app.Run(async context => - { - if (context.Request.Path.Equals("/SetStatusCodeAfterWrite")) - { - await context.Response.WriteAsync("Started_"); - try - { - context.Response.StatusCode = 200; - } - catch (InvalidOperationException) - { - await context.Response.WriteAsync("SetStatusCodeAfterWriteThrew_"); - } - await context.Response.WriteAsync("Finished"); - return; - } - else if (context.Request.Path.Equals("/SetHeaderAfterWrite")) - { - await context.Response.WriteAsync("Started_"); - try - { - context.Response.Headers["This will fail"] = "some value"; - } - catch (InvalidOperationException) - { - await context.Response.WriteAsync("SetHeaderAfterWriteThrew_"); - } - await context.Response.WriteAsync("Finished"); - return; - } - }); - } - - private void CheckEnvironmentVariable(IApplicationBuilder app) - { - app.Run(async context => - { - var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_VALUE"); - await context.Response.WriteAsync(variable); - }); - } - - private void CheckEnvironmentLongValueVariable(IApplicationBuilder app) - { - app.Run(async context => - { - var variable = Environment.GetEnvironmentVariable("ASPNETCORE_INPROCESS_TESTING_LONG_VALUE"); - await context.Response.WriteAsync(variable); - }); - } - - private void CheckAppendedEnvironmentVariable(IApplicationBuilder app) - { - app.Run(async context => - { - var variable = Environment.GetEnvironmentVariable("ProgramFiles"); - await context.Response.WriteAsync(variable); - }); - } - - private void CheckRemoveAuthEnvironmentVariable(IApplicationBuilder app) - { - app.Run(async context => - { - var variable = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_HTTPAUTH"); - await context.Response.WriteAsync(variable); - }); - } - private void ReadAndWriteSynchronously(IApplicationBuilder app) - { - app.Run(async context => - { - var t2 = Task.Run(() => WriteManyTimesToResponseBody(context)); - var t1 = Task.Run(() => ReadRequestBody(context)); - await Task.WhenAll(t1, t2); - }); - } - - private async Task ReadRequestBody(HttpContext context) + private async Task ReadRequestBody(HttpContext ctx) { var readBuffer = new byte[1]; - var result = await context.Request.Body.ReadAsync(readBuffer, 0, 1); + var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1); while (result != 0) { - result = await context.Request.Body.ReadAsync(readBuffer, 0, 1); + result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1); } } - private async Task WriteManyTimesToResponseBody(HttpContext context) + private async Task WriteManyTimesToResponseBody(HttpContext ctx) { for (var i = 0; i < 10000; i++) { - await context.Response.WriteAsync("hello world"); + await ctx.Response.WriteAsync("hello world"); } } - private void ReadAndWriteEcho(IApplicationBuilder app) + private async Task ReadAndWriteEcho(HttpContext ctx) { - app.Run(async context => + var readBuffer = new byte[4096]; + var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + while (result != 0) { - var readBuffer = new byte[4096]; - var result = await context.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); - while (result != 0) + await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); + result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + } + } + private async Task ReadAndFlushEcho(HttpContext ctx) + { + var readBuffer = new byte[4096]; + var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + while (result != 0) + { + await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); + await ctx.Response.Body.FlushAsync(); + result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + } + } + + private async Task ReadAndWriteEchoLines(HttpContext ctx) + { + if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType)) + { + ctx.Response.ContentType = contentType; + } + + //Send headers + await ctx.Response.Body.FlushAsync(); + + var reader = new StreamReader(ctx.Request.Body); + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(); + if (line == "") { - await context.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); - result = await context.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + return; } - }); + await ctx.Response.WriteAsync(line + Environment.NewLine); + await ctx.Response.Body.FlushAsync(); + } } - private void ReadAndWriteEchoTwice(IApplicationBuilder app) + private async Task ReadAndWriteEchoLinesNoBuffering(HttpContext ctx) { - app.Run(async context => + var feature = ctx.Features.Get(); + feature.DisableResponseBuffering(); + + if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType)) { - var readBuffer = new byte[4096]; - var result = await context.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); - while (result != 0) + ctx.Response.ContentType = contentType; + } + + //Send headers + await ctx.Response.Body.FlushAsync(); + + var reader = new StreamReader(ctx.Request.Body); + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(); + if (line == "") { - await context.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); - await context.Response.Body.FlushAsync(); - await context.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); - await context.Response.Body.FlushAsync(); - result = await context.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + return; } - }); + await ctx.Response.WriteAsync(line + Environment.NewLine); + } } - private void ReadAndWriteSlowConnection(IApplicationBuilder app) + private async Task ReadPartialBody(HttpContext ctx) { - app.Run(async context => + var data = new byte[5]; + var count = 0; + do { - var t2 = Task.Run(() => WriteResponseBodyAFewTimes(context)); - var t1 = Task.Run(() => ReadRequestBody(context)); - await Task.WhenAll(t1, t2); - }); + count += await ctx.Request.Body.ReadAsync(data, count, data.Length - count); + } while (count != data.Length); + await ctx.Response.Body.WriteAsync(data, 0, data.Length); } - private async Task WriteResponseBodyAFewTimes(HttpContext context) + private async Task SetHeaderFromBody(HttpContext ctx) + { + using (var reader = new StreamReader(ctx.Request.Body)) + { + var value = await reader.ReadToEndAsync(); + ctx.Response.Headers["BodyAsString"] = value; + await ctx.Response.WriteAsync(value); + } + } + + private async Task ReadAndWriteEchoTwice(HttpContext ctx) + { + var readBuffer = new byte[4096]; + var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + while (result != 0) + { + await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); + await ctx.Response.Body.FlushAsync(); + await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result)); + await ctx.Response.Body.FlushAsync(); + result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length); + } + } + + private async Task ReadAndWriteSlowConnection(HttpContext ctx) + { + var t2 = Task.Run(() => WriteResponseBodyAFewTimes(ctx)); + var t1 = Task.Run(() => ReadRequestBody(ctx)); + await Task.WhenAll(t1, t2); + } + + private async Task WriteResponseBodyAFewTimes(HttpContext ctx) { for (var i = 0; i < 100; i++) { - await context.Response.WriteAsync("hello world"); + await ctx.Response.WriteAsync("hello world"); } } - private void WebsocketRequest(IApplicationBuilder app) + private async Task ReadAndWriteCopyToAsync(HttpContext ctx) { - app.Run(async context => - { - await context.Response.WriteAsync("test"); - }); + await ctx.Request.Body.CopyToAsync(ctx.Response.Body); } - private void ReadAndWriteCopyToAsync(IApplicationBuilder app) + private async Task UpgradeFeatureDetection(HttpContext ctx) { - app.Run(async context => + if (ctx.Features.Get() != null) { - await context.Request.Body.CopyToAsync(context.Response.Body); - }); + await ctx.Response.WriteAsync("Enabled"); + } + else + { + await ctx.Response.WriteAsync("Disabled"); + } } - private void UpgradeFeatureDetection(IApplicationBuilder app) + private async Task TestReadOffsetWorks(HttpContext ctx) { - app.Run(async ctx => - { - if (ctx.Features.Get() != null) - { - await ctx.Response.WriteAsync("Enabled"); - } - else - { - await ctx.Response.WriteAsync("Disabled"); - } - }); + var buffer = new byte[11]; + ctx.Request.Body.Read(buffer, 0, 6); + ctx.Request.Body.Read(buffer, 6, 5); + + await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer)); } - private void TestReadOffsetWorks(IApplicationBuilder app) + private async Task TestInvalidReadOperations(HttpContext ctx) { - app.Run(async ctx => + var success = false; + if (ctx.Request.Path.StartsWithSegments("/NullBuffer")) { - var buffer = new byte[11]; - ctx.Request.Body.Read(buffer, 0, 6); - ctx.Request.Body.Read(buffer, 6, 5); - - await ctx.Response.WriteAsync(Encoding.UTF8.GetString(buffer)); - }); - } - - private void TestInvalidReadOperations(IApplicationBuilder app) - { - app.Run(async context => - { - var success = false; - if (context.Request.Path.StartsWithSegments("/NullBuffer")) - { - try - { - await context.Request.Body.ReadAsync(null, 0, 0); - } - catch (Exception) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidOffsetSmall")) - { - try - { - await context.Request.Body.ReadAsync(new byte[1], -1, 0); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidOffsetLarge")) - { - try - { - await context.Request.Body.ReadAsync(new byte[1], 2, 0); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountSmall")) - { - try - { - await context.Request.Body.ReadAsync(new byte[1], 0, -1); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountLarge")) - { - try - { - await context.Request.Body.ReadAsync(new byte[1], 0, -1); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountWithOffset")) - { - try - { - await context.Request.Body.ReadAsync(new byte[3], 1, 3); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - - - await context.Response.WriteAsync(success ? "Success" : "Failure"); - }); - } - - private void TestValidReadOperations(IApplicationBuilder app) - { - app.Run(async context => - { - var count = -1; - - if (context.Request.Path.StartsWithSegments("/NullBuffer")) - { - count = await context.Request.Body.ReadAsync(null, 0, 0); - } - else if (context.Request.Path.StartsWithSegments("/NullBufferPost")) - { - count = await context.Request.Body.ReadAsync(null, 0, 0); - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountZeroRead")) - { - count = await context.Request.Body.ReadAsync(new byte[1], 0, 0); - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountZeroReadPost")) - { - count = await context.Request.Body.ReadAsync(new byte[1], 0, 0); - } - - await context.Response.WriteAsync(count == 0 ? "Success" : "Failure"); - }); - } - - private void TestInvalidWriteOperations(IApplicationBuilder app) - { - app.Run(async context => - { - var success = false; - - if (context.Request.Path.StartsWithSegments("/InvalidOffsetSmall")) - { - try - { - await context.Response.Body.WriteAsync(new byte[1], -1, 0); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidOffsetLarge")) - { - try - { - await context.Response.Body.WriteAsync(new byte[1], 2, 0); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountSmall")) - { - try - { - await context.Response.Body.WriteAsync(new byte[1], 0, -1); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountLarge")) - { - try - { - await context.Response.Body.WriteAsync(new byte[1], 0, -1); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - else if (context.Request.Path.StartsWithSegments("/InvalidCountWithOffset")) - { - try - { - await context.Response.Body.WriteAsync(new byte[3], 1, 3); - } - catch (ArgumentOutOfRangeException) - { - success = true; - } - } - - await context.Response.WriteAsync(success ? "Success" : "Failure"); - }); - } - - private void TestValidWriteOperations(IApplicationBuilder app) - { - app.Run(async context => - { - - if (context.Request.Path.StartsWithSegments("/NullBuffer")) - { - await context.Response.Body.WriteAsync(null, 0, 0); - } - else if (context.Request.Path.StartsWithSegments("/NullBufferPost")) - { - await context.Response.Body.WriteAsync(null, 0, 0); - } - - await context.Response.WriteAsync("Success"); - }); - } - - private void LargeResponseFile(IApplicationBuilder app) - { - app.Run(async ctx => - { - var tempFile = Path.GetTempFileName(); - var fileContent = new string('a', 200000); - var fileStream = File.OpenWrite(tempFile); - - for (var i = 0; i < 1000; i++) - { - await fileStream.WriteAsync(Encoding.UTF8.GetBytes(fileContent), 0, fileContent.Length); - } - fileStream.Close(); - - await ctx.Response.SendFileAsync(tempFile, 0, null); - - // Try to delete the file from the temp directory. If it fails, don't report an error - // to the application. File should eventually be cleaned up from the temp directory - // by OS. try { - File.Delete(tempFile); + await ctx.Request.Body.ReadAsync(null, 0, 0); } catch (Exception) { + success = true; } - }); + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetSmall")) + { + try + { + await ctx.Request.Body.ReadAsync(new byte[1], -1, 0); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetLarge")) + { + try + { + await ctx.Request.Body.ReadAsync(new byte[1], 2, 0); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountSmall")) + { + try + { + await ctx.Request.Body.ReadAsync(new byte[1], 0, -1); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountLarge")) + { + try + { + await ctx.Request.Body.ReadAsync(new byte[1], 0, -1); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountWithOffset")) + { + try + { + await ctx.Request.Body.ReadAsync(new byte[3], 1, 3); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + + + await ctx.Response.WriteAsync(success ? "Success" : "Failure"); } - private void BasePath(IApplicationBuilder app) + private async Task TestValidReadOperations(HttpContext ctx) { - app.Run(async ctx => { await ctx.Response.WriteAsync(AppDomain.CurrentDomain.BaseDirectory); }); + var count = -1; + + if (ctx.Request.Path.StartsWithSegments("/NullBuffer")) + { + count = await ctx.Request.Body.ReadAsync(null, 0, 0); + } + else if (ctx.Request.Path.StartsWithSegments("/NullBufferPost")) + { + count = await ctx.Request.Body.ReadAsync(null, 0, 0); + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountZeroRead")) + { + count = await ctx.Request.Body.ReadAsync(new byte[1], 0, 0); + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountZeroReadPost")) + { + count = await ctx.Request.Body.ReadAsync(new byte[1], 0, 0); + } + + await ctx.Response.WriteAsync(count == 0 ? "Success" : "Failure"); } + + private async Task TestInvalidWriteOperations(HttpContext ctx) + { + var success = false; + + if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetSmall")) + { + try + { + await ctx.Response.Body.WriteAsync(new byte[1], -1, 0); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidOffsetLarge")) + { + try + { + await ctx.Response.Body.WriteAsync(new byte[1], 2, 0); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountSmall")) + { + try + { + await ctx.Response.Body.WriteAsync(new byte[1], 0, -1); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountLarge")) + { + try + { + await ctx.Response.Body.WriteAsync(new byte[1], 0, -1); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + else if (ctx.Request.Path.StartsWithSegments("/InvalidCountWithOffset")) + { + try + { + await ctx.Response.Body.WriteAsync(new byte[3], 1, 3); + } + catch (ArgumentOutOfRangeException) + { + success = true; + } + } + + await ctx.Response.WriteAsync(success ? "Success" : "Failure"); + } + + private async Task TestValidWriteOperations(HttpContext ctx) + { + if (ctx.Request.Path.StartsWithSegments("/NullBuffer")) + { + await ctx.Response.Body.WriteAsync(null, 0, 0); + } + else if (ctx.Request.Path.StartsWithSegments("/NullBufferPost")) + { + await ctx.Response.Body.WriteAsync(null, 0, 0); + } + + await ctx.Response.WriteAsync("Success"); + } + + private async Task LargeResponseFile(HttpContext ctx) + { + var tempFile = Path.GetTempFileName(); + var fileContent = new string('a', 200000); + var fileStream = File.OpenWrite(tempFile); + + for (var i = 0; i < 1000; i++) + { + await fileStream.WriteAsync(Encoding.UTF8.GetBytes(fileContent), 0, fileContent.Length); + } + fileStream.Close(); + + await ctx.Response.SendFileAsync(tempFile, 0, null); + + // Try to delete the file from the temp directory. If it fails, don't report an error + // to the application. File should eventually be cleaned up from the temp directory + // by OS. + try + { + File.Delete(tempFile); + } + catch (Exception) + { + } + } + + private async Task BasePath(HttpContext ctx) + { + await ctx.Response.WriteAsync(AppDomain.CurrentDomain.BaseDirectory); + } + + private async Task Shutdown(HttpContext ctx) + { + await ctx.Response.WriteAsync("Shutting down"); + ctx.RequestServices.GetService().StopApplication(); + } + + private async Task GetServerVariableStress(HttpContext ctx) + { + // This test simulates the scenario where native Flush call is being + // executed on background thread while request thread calls GetServerVariable + // concurrent native calls may cause native object corruption + + var serverVariableFeature = ctx.Features.Get(); + await ctx.Response.WriteAsync("Response Begin"); + for (int i = 0; i < 1000; i++) + { + await ctx.Response.WriteAsync(serverVariableFeature["REMOTE_PORT"]); + await ctx.Response.Body.FlushAsync(); + } + await ctx.Response.WriteAsync("Response End"); + } + + private async Task CommandLineArgs(HttpContext ctx) + { + await ctx.Response.WriteAsync(string.Join("|", Environment.GetCommandLineArgs().Skip(1))); + } + + public Task HttpsHelloWorld(HttpContext ctx) => + ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"]); } } diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/web.config b/src/IISIntegration/test/WebSites/InProcessWebSite/web.config index 8ba96a4e9e..2a9bd223c3 100644 --- a/src/IISIntegration/test/WebSites/InProcessWebSite/web.config +++ b/src/IISIntegration/test/WebSites/InProcessWebSite/web.config @@ -1,15 +1,15 @@ - + - + - + diff --git a/src/IISIntegration/test/WebSites/InProcessWebSite/wwwroot/static.txt b/src/IISIntegration/test/WebSites/InProcessWebSite/wwwroot/static.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj index 0b96c98c36..14beb7394e 100644 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj +++ b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/OutOfProcessWebSite.csproj @@ -11,6 +11,15 @@ + + + + + + + + + diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Program.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Program.cs index 18104fa30a..90895237d2 100644 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Program.cs +++ b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Program.cs @@ -1,27 +1,44 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.IO; +using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; -namespace TestSites +namespace TestSite { public static class Program { - public static void Main(string[] args) + public static int Main(string[] args) + { + var mode = args.FirstOrDefault(); + switch (mode) + { + case "CreateFile": + File.WriteAllText(args[1], ""); + return StartServer(); + } + + return StartServer(); + } + + private static int StartServer() { var host = new WebHostBuilder() - .ConfigureLogging((_, factory) => - { - factory.AddConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Information); - }) + .ConfigureLogging( + (_, factory) => { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Information); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() - .UseStartup(typeof(Program).Assembly.FullName) + .UseStartup() .UseKestrel() .Build(); host.Run(); + return 0; } } } diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json +++ b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Properties/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Startup.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Startup.cs new file mode 100644 index 0000000000..de54a85a8a --- /dev/null +++ b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/Startup.cs @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.IISIntegration.FunctionalTests; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace TestSite +{ + public partial class Startup + { + private IServerAddressesFeature _serverAddresses; + + public void Configure(IApplicationBuilder app) + { + TestStartup.Register(app, this); + + _serverAddresses = app.ServerFeatures.Get(); + } + + public Task Path(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.Path.Value); + + public Task Query(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.QueryString.Value); + + public Task BodyLimit(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Features.Get()?.MaxRequestBodySize?.ToString() ?? "null"); + + + public Task HelloWorld(HttpContext ctx) => ctx.Response.WriteAsync("Hello World"); + + public Task HttpsHelloWorld(HttpContext ctx) => + ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"]); + + public Task Anonymous(HttpContext context) => context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated); + + public Task Restricted(HttpContext context) + { + if (context.User.Identity.IsAuthenticated) + { + Assert.IsType(context.User); + return context.Response.WriteAsync(context.User.Identity.AuthenticationType); + } + else + { + return context.ChallengeAsync(IISDefaults.AuthenticationScheme); + } + } + + public Task Forbidden(HttpContext context) => context.ForbidAsync(IISDefaults.AuthenticationScheme); + + public Task RestrictedNTLM(HttpContext context) + { + if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal)) + { + return context.Response.WriteAsync("NTLM"); + } + else + { + return context.ChallengeAsync(IISDefaults.AuthenticationScheme); + } + } + + public Task UpgradeFeatureDetection(HttpContext context) => + context.Response.WriteAsync(context.Features.Get() != null? "Enabled": "Disabled"); + + public Task CheckRequestHandlerVersion(HttpContext context) + { + // We need to check if the aspnetcorev2_outofprocess dll is loaded by iisexpress.exe + // As they aren't in the same process, we will try to delete the file and expect a file + // in use error + try + { + File.Delete(context.Request.Headers["ANCMRHPath"]); + } + catch(UnauthorizedAccessException) + { + // TODO calling delete on the file will succeed when running with IIS + return context.Response.WriteAsync("Hello World"); + } + + return context.Response.WriteAsync(context.Request.Headers["ANCMRHPath"]); + } + + private async Task ProcessId(HttpContext context) + { + await context.Response.WriteAsync(Process.GetCurrentProcess().Id.ToString()); + } + + private async Task ServerAddresses(HttpContext context) + { + await context.Response.WriteAsync(string.Join(",", _serverAddresses.Addresses)); + } + } +} diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHelloWorld.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHelloWorld.cs deleted file mode 100644 index a406626ede..0000000000 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHelloWorld.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace TestSites -{ - public class StartupHelloWorld - { - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - app.Run(async ctx => - { - if (ctx.Request.Path.Value.StartsWith("/Path")) - { - await ctx.Response.WriteAsync(ctx.Request.Path.Value); - return; - } - if (ctx.Request.Path.Value.StartsWith("/Query")) - { - await ctx.Response.WriteAsync(ctx.Request.QueryString.Value); - return; - } - if (ctx.Request.Path.Value.StartsWith("/BodyLimit")) - { - await ctx.Response.WriteAsync( - ctx.Features.Get()?.MaxRequestBodySize?.ToString() ?? "null"); - return; - } - - if (ctx.Request.Path.StartsWithSegments("/Auth")) - { - var iisAuth = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_HTTPAUTH"); - var authProvider = ctx.RequestServices.GetService(); - var authScheme = (await authProvider.GetAllSchemesAsync()).SingleOrDefault(); - if (string.IsNullOrEmpty(iisAuth)) - { - await ctx.Response.WriteAsync("backcompat;" + (authScheme?.Name ?? "null")); - } - else - { - await ctx.Response.WriteAsync("latest;" + (authScheme?.Name ?? "null")); - } - return; - } - - await ctx.Response.WriteAsync("Hello World"); - }); - } - } -} diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHttpsHelloWorld.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHttpsHelloWorld.cs deleted file mode 100644 index 3d9c7a92f7..0000000000 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupHttpsHelloWorld.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace TestSites -{ - public class StartupHttpsHelloWorld - { - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - app.Run(ctx => - { - if (ctx.Request.Path.Equals(new PathString("/checkclientcert"))) - { - return ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"] - + "; has cert? " + (ctx.Connection.ClientCertificate != null)); - } - return ctx.Response.WriteAsync("Scheme:" + ctx.Request.Scheme + "; Original:" + ctx.Request.Headers["x-original-proto"]); - }); - } - } -} \ No newline at end of file diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupNtlmAuthentication.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupNtlmAuthentication.cs deleted file mode 100644 index 29098ca702..0000000000 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupNtlmAuthentication.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Security.Principal; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Server.IISIntegration; -using Microsoft.Extensions.Logging; -using Xunit; - -namespace TestSites -{ - public class StartupNtlmAuthentication - { - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - // Simple error page without depending on Diagnostics. - 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) - { - Assert.IsType(context.User); - return context.Response.WriteAsync(context.User.Identity.AuthenticationType); - } - else - { - return context.ChallengeAsync(IISDefaults.AuthenticationScheme); - } - } - - if (context.Request.Path.Equals("/Forbidden")) - { - return context.ForbidAsync(IISDefaults.AuthenticationScheme); - } - - if (context.Request.Path.Equals("/RestrictedNTLM")) - { - if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal)) - { - return context.Response.WriteAsync("NTLM"); - } - else - { - return context.ChallengeAsync(IISDefaults.AuthenticationScheme); - } - } - - return context.Response.WriteAsync("Hello World"); - }); - } - } -} diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupUpgradeFeatureDetection.cs b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupUpgradeFeatureDetection.cs deleted file mode 100644 index 81bc3149a0..0000000000 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/StartupUpgradeFeatureDetection.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace TestSites -{ - public class StartupUpgradeFeatureDetection - { - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - app.Run(async ctx => - { - if (ctx.Features.Get() != null) - { - await ctx.Response.WriteAsync("Enabled"); - } - else - { - await ctx.Response.WriteAsync("Disabled"); - } - }); - } - } -} diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/web.config b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/web.config deleted file mode 100644 index 8286b650fe..0000000000 --- a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/web.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/IISIntegration/test/WebSites/OutOfProcessWebSite/wwwroot/static.txt b/src/IISIntegration/test/WebSites/OutOfProcessWebSite/wwwroot/static.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/OverriddenServerWebSite.csproj b/src/IISIntegration/test/WebSites/OverriddenServerWebSite/OverriddenServerWebSite.csproj deleted file mode 100644 index 4332ea3fd1..0000000000 --- a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/OverriddenServerWebSite.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - $(StandardTestTfms) - - - - - - - - - - - diff --git a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/web.config b/src/IISIntegration/test/WebSites/OverriddenServerWebSite/web.config deleted file mode 100644 index f125d57107..0000000000 --- a/src/IISIntegration/test/WebSites/OverriddenServerWebSite/web.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/IISIntegration/test/WebSites/StressTestWebSite/Properties/launchSettings.json b/src/IISIntegration/test/WebSites/StressTestWebSite/Properties/launchSettings.json index 6d5ce43f73..246b7a0b47 100644 --- a/src/IISIntegration/test/WebSites/StressTestWebSite/Properties/launchSettings.json +++ b/src/IISIntegration/test/WebSites/StressTestWebSite/Properties/launchSettings.json @@ -12,13 +12,15 @@ "commandName": "Executable", "executablePath": "$(IISExpressPath)", "commandLineArgs": "$(IISExpressArguments)", - "nativeDebugging": true, "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ANCM_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } }, "ANCM IIS": { @@ -27,10 +29,13 @@ "commandLineArgs": "$(IISArguments)", "environmentVariables": { "IIS_SITE_PATH": "$(MSBuildThisFileDirectory)", - "ANCM_PATH": "$(TargetDir)$(AncmPath)", + "ANCM_PATH": "$(AspNetCoreModuleV1ShimDll)", + "ANCMV2_PATH": "$(AspNetCoreModuleV2ShimDll)", + "ASPNETCORE_MODULE_OUTOFPROCESS_HANDLER": "$(AspNetCoreModuleV2OutOfProcessHandlerDll)", "LAUNCHER_ARGS": "$(TargetPath)", "ASPNETCORE_ENVIRONMENT": "Development", - "LAUNCHER_PATH": "$(DotNetPath)" + "LAUNCHER_PATH": "$(DotNetPath)", + "ASPNETCORE_MODULE_DEBUG": "console" } } } diff --git a/src/IISIntegration/test/WebSites/StressTestWebSite/Startup.cs b/src/IISIntegration/test/WebSites/StressTestWebSite/Startup.cs index be0969ec77..68f6eb1f77 100644 --- a/src/IISIntegration/test/WebSites/StressTestWebSite/Startup.cs +++ b/src/IISIntegration/test/WebSites/StressTestWebSite/Startup.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Threading; using System.Text; using System.Net.WebSockets; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; diff --git a/src/IISIntegration/test/WebSites/StressTestWebSite/StressTestWebSite.csproj b/src/IISIntegration/test/WebSites/StressTestWebSite/StressTestWebSite.csproj index 3566143fcd..25ae032221 100644 --- a/src/IISIntegration/test/WebSites/StressTestWebSite/StressTestWebSite.csproj +++ b/src/IISIntegration/test/WebSites/StressTestWebSite/StressTestWebSite.csproj @@ -4,6 +4,7 @@ $(StandardTestTfms) + true @@ -11,6 +12,11 @@ + + + + + diff --git a/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs b/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs new file mode 100644 index 0000000000..b5d7f0305f --- /dev/null +++ b/src/IISIntegration/test/WebSites/shared/SharedStartup/Startup.shared.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace TestSite +{ + public partial class Startup + { + public void ConfigureServices(IServiceCollection serviceCollection) + { + serviceCollection.AddResponseCompression(); + } + + private async Task HostingEnvironment(HttpContext ctx) + { + var hostingEnv = ctx.RequestServices.GetService(); + + await ctx.Response.WriteAsync("ContentRootPath " + hostingEnv.ContentRootPath + Environment.NewLine); + await ctx.Response.WriteAsync("WebRootPath " + hostingEnv.WebRootPath + Environment.NewLine); + await ctx.Response.WriteAsync("CurrentDirectory " + Environment.CurrentDirectory); + } + + private async Task ConsoleWrite(HttpContext ctx) + { + Console.WriteLine("TEST MESSAGE"); + + await ctx.Response.WriteAsync("Hello World"); + } + + private async Task ConsoleErrorWrite(HttpContext ctx) + { + Console.Error.WriteLine("TEST MESSAGE"); + + await ctx.Response.WriteAsync("Hello World"); + } + + public async Task Auth(HttpContext ctx) + { + var authProvider = ctx.RequestServices.GetService(); + var authScheme = (await authProvider.GetAllSchemesAsync()).SingleOrDefault(); + + await ctx.Response.WriteAsync(authScheme?.Name ?? "null"); + if (ctx.User.Identity.Name != null) + { + await ctx.Response.WriteAsync(":" + ctx.User.Identity.Name); + } + } + + public async Task GetClientCert(HttpContext context) + { + var clientCert = context.Connection.ClientCertificate; + await context.Response.WriteAsync(clientCert != null ? $"Enabled;{clientCert.GetCertHashString()}" : "Disabled"); + } + + private static int _waitingRequestCount; + + public Task WaitForAbort(HttpContext context) + { + Interlocked.Increment(ref _waitingRequestCount); + try + { + context.RequestAborted.WaitHandle.WaitOne(); + return Task.CompletedTask; + } + finally + { + Interlocked.Decrement(ref _waitingRequestCount); + } + } + + public Task Abort(HttpContext context) + { + context.Abort(); + return Task.CompletedTask; + } + + public async Task WaitingRequestCount(HttpContext context) + { + await context.Response.WriteAsync(_waitingRequestCount.ToString()); + } + + public Task CreateFile(HttpContext context) + { + var hostingEnv = context.RequestServices.GetService(); + File.WriteAllText(System.IO.Path.Combine(hostingEnv.ContentRootPath, "Started.txt"), ""); + return Task.CompletedTask; + } + + public Task OverrideServer(HttpContext context) + { + context.Response.Headers["Server"] = "MyServer/7.8"; + return Task.CompletedTask; + } + + public void CompressedData(IApplicationBuilder builder) + { + builder.UseResponseCompression(); + // write random bytes to check that compressed data is passed through + builder.Run( + async context => + { + context.Response.ContentType = "text/html"; + await context.Response.Body.WriteAsync(new byte[100], 0, 100); + }); + } + } +} diff --git a/src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/Constants.cs b/src/IISIntegration/test/WebSites/shared/WebSockets/Constants.cs similarity index 91% rename from src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/Constants.cs rename to src/IISIntegration/test/WebSites/shared/WebSockets/Constants.cs index bcf5462558..95b53003cc 100644 --- a/src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/Constants.cs +++ b/src/IISIntegration/test/WebSites/shared/WebSockets/Constants.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ANCMStressTestApp +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { public static class Constants { diff --git a/src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/HandshakeHelpers.cs b/src/IISIntegration/test/WebSites/shared/WebSockets/HandshakeHelpers.cs similarity index 96% rename from src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/HandshakeHelpers.cs rename to src/IISIntegration/test/WebSites/shared/WebSockets/HandshakeHelpers.cs index 331f415013..b079ea3f85 100644 --- a/src/IISIntegration/test/WebSites/StressTestWebSite/WebSockets/HandshakeHelpers.cs +++ b/src/IISIntegration/test/WebSites/shared/WebSockets/HandshakeHelpers.cs @@ -6,9 +6,8 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Text; -namespace ANCMStressTestApp +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { - // Removed all the internal static class HandshakeHelpers { public static IEnumerable> GenerateResponseHeaders(string key) diff --git a/src/IISIntegration/test/WebSites/shared/WebSockets/TestStartup.cs b/src/IISIntegration/test/WebSites/shared/WebSockets/TestStartup.cs new file mode 100644 index 0000000000..b1604e367a --- /dev/null +++ b/src/IISIntegration/test/WebSites/shared/WebSockets/TestStartup.cs @@ -0,0 +1,38 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.IISIntegration.FunctionalTests +{ + public static class TestStartup + { + public static void Register(IApplicationBuilder app, object startup) + { + var type = startup.GetType(); + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + var parameters = method.GetParameters(); + if (method.Name != "Configure" && + parameters.Length == 1) + { + Action appfunc = null; + if (parameters[0].ParameterType == typeof(IApplicationBuilder)) + { + appfunc = innerAppBuilder => method.Invoke(startup, new[] { innerAppBuilder }); + } + else if (parameters[0].ParameterType == typeof(HttpContext)) + { + appfunc = innerAppBuilder => innerAppBuilder.Run(ctx => (Task)method.Invoke(startup, new[] { ctx })); + } + + if (appfunc != null) + { + app.Map("/" + method.Name, appfunc); + } + } + } + } + } +} diff --git a/src/IISIntegration/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj b/src/IISIntegration/test/gtest/gtest.vcxproj similarity index 61% rename from src/IISIntegration/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj rename to src/IISIntegration/test/gtest/gtest.vcxproj index 8f12548e9f..924f26337d 100644 --- a/src/IISIntegration/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj +++ b/src/IISIntegration/test/gtest/gtest.vcxproj @@ -18,44 +18,47 @@ x64 + + + + 15.0 - {0692D963-DB10-4387-B3EA-460FBB9BD9A3} + {CAC1267B-8778-4257-AAC6-CAF481723B01} Win32Proj - AspNetCoreModuleTests + gtest 10.0.15063.0 - NativeUnitTestProject - DynamicLibrary + StaticLibrary true v141 Unicode - false + gtestd - DynamicLibrary + StaticLibrary false v141 true Unicode - false + gtest - DynamicLibrary + StaticLibrary true v141 Unicode - false + gtestd - DynamicLibrary + StaticLibrary false v141 true Unicode - false + gtest @@ -75,109 +78,102 @@ - - true - true + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(VC_SourcePath); + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(VC_SourcePath); + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ - true + false + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(VC_SourcePath); + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ - true + false + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(VC_SourcePath); + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ - - - Use - Level3 - Disabled - $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib - _DEBUG;%(PreprocessorDefinitions) - true - MultiThreadedDebug - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) - - - Use + NotUsing Level3 Disabled - $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib - WIN32;_DEBUG;%(PreprocessorDefinitions) - true + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreadedDebug Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + true + + + + + NotUsing + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Windows + true + NotUsing Level3 - Use MaxSpeed true true - $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib - WIN32;NDEBUG;%(PreprocessorDefinitions) - true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreaded Windows true true - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + true + NotUsing Level3 - Use MaxSpeed true true - $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\CommonLib;..\..\src\AspNetCoreModuleV2\IISLib - NDEBUG;%(PreprocessorDefinitions) - true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreaded Windows true true - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + true - - - - - - - Create - Create - Create - Create - - - - - - - {55494e58-e061-4c4c-a0a8-837008e72f85} - - - {4787a64f-9a3e-4867-a55a-70cb4b2b2ffe} - - - diff --git a/src/IISIntegration/tools/GenerateNativeAssets.ps1 b/src/IISIntegration/tools/GenerateNativeAssets.ps1 new file mode 100644 index 0000000000..f4b98959ed --- /dev/null +++ b/src/IISIntegration/tools/GenerateNativeAssets.ps1 @@ -0,0 +1,150 @@ +$targetFile = Join-Path (Split-Path -parent $PSCommandPath) "..\build\assets.props"; + +$platforms = @( + @{ + Platform = "x64"; + VCPlatform = "x64"; + }, + @{ + Platform = "x86"; + VCPlatform = "Win32"; + } +); +$srcDir = "`$(MSBuildThisFileDirectory)..\src"; +$projects = @( + @{ + ProjectDirectory = "$srcDir\AspNetCoreModuleV1\AspNetCore"; + ProjectName = "AspNetCore.vcxproj"; + NativeAsset = "aspnetcore"; + BaseOutputPath = "AspNetCoreModuleV1" + PropetyName = "AspNetCoreModuleV1Shim" + }, + @{ + ProjectDirectory = "$srcDir\AspNetCoreModuleV2\AspNetCore"; + ProjectName = "`AspNetCore.vcxproj"; + NativeAsset = "aspnetcorev2"; + BaseOutputPath = "AspNetCoreModuleV2" + PropetyName = "AspNetCoreModuleV2Shim" + }, + @{ + ProjectDirectory = "$srcDir\AspNetCoreModuleV2\InProcessRequestHandler"; + ProjectName = "InProcessRequestHandler.vcxproj"; + NativeAsset = "aspnetcorev2_inprocess"; + BaseOutputPath = "AspNetCoreModuleV2"; + PropetyName = "AspNetCoreModuleV2InProcessHandler" + }, + @{ + ProjectDirectory = "$srcDir\AspNetCoreModuleV2\OutOfProcessRequestHandler"; + ProjectName = "OutOfProcessRequestHandler.vcxproj"; + NativeAsset = "aspnetcorev2_outofprocess"; + BaseOutputPath = "AspNetCoreModuleV2"; + PackageSubPath = "`$(AspNetCoreModuleOutOfProcessVersion)\"; + PropetyName = "AspNetCoreModuleV2OutOfProcessHandler" + } +); +$currentPlatform = @{ + Platform = "`$(NativePlatform)"; + VCPlatform = "`$(NativeVCPlatform)"; +}; +$components = @(); +$shimComponents = @(); +$inProcessComponents = @(); +$runShimComponents = @(); +$runInProcessComponents = @(); +$properties = @(); + +function CopyProperties($from, $to) +{ + foreach ($key in $from.Keys) + { + $to.Add($key, $from.$key); + } +} + +function Write-Group($group, $name) +{ + return $( + foreach ($item in $group){ + " <$name$(foreach ($pair in $item.GetEnumerator()) { + "`n $($pair.Key)=`"`"$($pair.Value)`"`""}) + />`n"}); +} +function Write-Properties($group) +{ + return $( + foreach ($item in $group){ + "`n <$($item.Name)>$($item.Value)"}); +} + +function New-Component($project, $platform) +{ + $component = [ordered]@{}; + CopyProperties -from $platform -to $component; + CopyProperties -from $project -to $component; + CopyProperties -from @{ + Include = "$($project.ProjectDirectory)\$($project.ProjectName)"; + DllLocation = "$($project.ProjectDirectory)\bin\`$(Configuration)\$($platform.VCPlatform)\$($project.NativeAsset).dll"; + PdbLocation = "$($project.ProjectDirectory)\bin\`$(Configuration)\$($platform.VCPlatform)\$($project.NativeAsset).pdb"; + } -to $component; + + return $component; +} + +foreach ($project in $projects) +{ + foreach ($platform in $platforms) + { + $component = New-Component $project $platform; + $components += $component; + + if ($project.ProjectName.Contains("InProcess")) + { + $inProcessComponents += $component; + } + else + { + $shimComponents += $component; + } + } + + $properties += @{ + Name = "$($project.PropetyName)Dll"; + Value = "$($project.ProjectDirectory)\bin\`$(Configuration)\`$(NativeVCPlatform)\$($project.NativeAsset).dll"; + }; + + $runComponent = New-Component $project $currentPlatform; + + if ($project.ProjectName.Contains("InProcess")) + { + $runInProcessComponents += $runComponent; + } + else + { + $runShimComponents += $runComponent; + } +} + +$content = @" + + + + true + x64 + `$(Platform) + Win32 + `$(NativePlatform) + + +$(Write-Group $components "Components" ) +$(Write-Group $shimComponents "ShimComponents") +$(Write-Group $inProcessComponents "InProcessComponents") +$(Write-Group $runShimComponents "RunShimComponents") +$(Write-Group $runInProcessComponents "RunInProcessComponents") + + +$(Write-Properties $properties) + + +"@; + +[IO.File]::WriteAllLines($targetFile, $content) diff --git a/src/IISIntegration/tools/SetupTestEnvironment.ps1 b/src/IISIntegration/tools/SetupTestEnvironment.ps1 new file mode 100644 index 0000000000..3adaf79045 --- /dev/null +++ b/src/IISIntegration/tools/SetupTestEnvironment.ps1 @@ -0,0 +1,135 @@ +param($Mode) + +$DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps" +if (!($DumpFolder)) +{ + $DumpFolder = "$PSScriptRoot\..\artifacts\dumps" +} +if (!(Test-Path $DumpFolder)) +{ + New-Item $DumpFolder -ItemType Directory; +} +$DumpFolder = Resolve-Path $DumpFolder + +$LogsFolder = "$PSScriptRoot\..\artifacts\logs" +if (!(Test-Path $LogsFolder)) +{ + New-Item $LogsFolder -ItemType Directory; +} +$LogsFolder = Resolve-Path $LogsFolder + +$werHive = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting"; +$ldHive = "$werHive\LocalDumps"; + + +function Setup-appverif($application) +{ + appverif.exe -enable Exceptions Handles Heaps Leak Locks Memory Threadpool TLS SRWLock -for $application + $level = 0x1E1; + $codes = @( + # Exceptions + 0x650, + # Handles + 0x300, 0x301, 0x302, 0x303, 0x304, # 0x305, + # Heaps + 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F, 0x010, 0x011, 0x012, 0x013, 0x014, + # Leak + 0x900, 0x901, 0x902, 0x903, 0x904, 0x905, 0x906, + # Locks + 0x200, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x210, 0x211, 0x212, 0x213, 0x214, 0x215, + # Memory + 0x600, 0x601, 0x602, 0x603, 0x604, 0x605, 0x606, 0x607, 0x608, 0x609, 0x60A, 0x60B, 0x60C, 0x60D, 0x60E, 0x60F, 0x610, 0x612, 0x613, 0x614, 0x615, 0x616, 0x617, 0x618, 0x619, 0x61A, 0x61B, 0x61C, 0x61D, 0x61E, + # SRWLock + 0x250, 0x251, 0x252, 0x253, 0x254, 0x255, 0x256, 0x257, + # TSL + 0x350, 0x351, 0x352, + # ThreadPool + 0x700, 0x701, 0x702, 0x703, 0x704, 0x705, 0x706, 0x707, 0x708, 0x709, 0x70A, 0x70B, 0x70C, 0x70D + ); + + setx APPVERIFIER_ENABLED_CODES "$codes"; + setx APPVERIFIER_LEVEL $level; + appverif.exe -configure $codes -for $application -with ErrorReport=$level + + # 0x305, - disabled because coreclr.dll!SetThreadName(void *) ofthen passes invalid handle (0xffffff) + appverif.exe -configure 0x305 -for $application -with ErrorReport=0 +} + +function Shutdown-appverif($application) +{ + setx APPVERIFIER_ENABLED_CODES "NONE"; + setx APPVERIFIER_LEVEL "NONE"; + + appverif.exe -disable * -for $application +} + +function Setup-Dumps() +{ + if (!(Test-Path $ldHive )) + { + New-Item -Path $werHive -Name LocalDumps + } + + Move-Item $env:windir\System32\vsjitdebugger.exe $env:windir\System32\_vsjitdebugger.exe; + + New-ItemProperty $werHive -Name "DontShowUI" -Value 1 -PropertyType "DWORD" -Force; + + New-ItemProperty $ldHive -Name "DumpFolder" -Value $DumpFolder -PropertyType "ExpandString" -Force; + New-ItemProperty $ldHive -Name "DumpCount" -Value 15 -PropertyType "DWORD" -Force; + New-ItemProperty $ldHive -Name "DumpType" -Value 2 -PropertyType "DWORD" -Force; + + Restart-Service WerSvc +} + +function Shutdown-Dumps() +{ + Move-Item $env:windir\System32\_vsjitdebugger.exe $env:windir\System32\vsjitdebugger.exe; + + Remove-Item $ldHive -Recurse -Force + + New-ItemProperty $werHive -Name "DontShowUI" -Value 0 -PropertyType "DWORD" -Force; + + $cdb = "c:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" + if (!(Test-Path $cdb)) + { + $downloadedFile = [System.IO.Path]::GetTempFileName(); + $downloadedFile = "$downloadedFile.exe"; + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?linkid=870807" -OutFile $downloadedFile; + & $downloadedFile /features OptionId.WindowsDesktopDebuggers /norestart /q; + } + + foreach ($dump in (Get-ChildItem -Path $DumpFolder -Filter "*.dmp")) + { + if (Test-Path $cdb) + { + & $cdb -z $dump.FullName -y "https://msdl.microsoft.com/download/symbols" -c ".loadby sos coreclr;!sym noisy;.reload /f;.dumpcab -a $($dump.FullName).cab;q;" + Remove-Item $dump.FullName + } + } +} + +if ($Mode -eq "Setup") +{ + Setup-appverif w3wp.exe + Setup-appverif iisexpress.exe + + Setup-Dumps; +} + +if ($Mode -eq "SetupDumps") +{ + Shutdown-appverif w3wp.exe + Shutdown-appverif iisexpress.exe + + Setup-Dumps; +} + +if ($Mode -eq "Shutdown") +{ + Shutdown-appverif w3wp.exe + Shutdown-appverif iisexpress.exe + + Shutdown-Dumps; +} + +Exit 0; \ No newline at end of file diff --git a/src/IISIntegration/tools/UpdateIISExpressCertificate.ps1 b/src/IISIntegration/tools/UpdateIISExpressCertificate.ps1 new file mode 100644 index 0000000000..9034cf8f75 --- /dev/null +++ b/src/IISIntegration/tools/UpdateIISExpressCertificate.ps1 @@ -0,0 +1,20 @@ +$cert = New-SelfSignedCertificate -DnsName "localhost", "localhost" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(5) +$thumb = $cert.GetCertHashString() + +$Store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList 'root', 'LocalMachine' +$Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) +$Store.Add($cert) +$Store.Close() + +$tempFile = [System.IO.Path]::GetTempFileName(); +$content = ""; + +for ($i=44300; $i -le 44399; $i++) { + $content += "http delete sslcert ipport=0.0.0.0:$i`n"; + $content += "http add sslcert ipport=0.0.0.0:$i certhash=$thumb appid=`{214124cd-d05b-4309-9af9-9caa44b2b74a`}`n"; +} + +[IO.File]::WriteAllLines($tempFile, $content) + +netsh -f $tempFile +Remove-Item $tempFile; \ No newline at end of file diff --git a/src/IISIntegration/tools/stresstest.ps1 b/src/IISIntegration/tools/stresstest.ps1 deleted file mode 100644 index 981c6fcf44..0000000000 --- a/src/IISIntegration/tools/stresstest.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -########################################################## -# NOTE: -# For running test automation, following prerequisite required: -# -# 1. On Win7, powershell should be upgraded to 4.0 -# https://social.technet.microsoft.com/wiki/contents/articles/21016.how-to-install-windows-powershell-4-0.aspx -# 2. url-rewrite should be installed -# 3. makecert.exe tools should be available -########################################################## - -# Replace aspnetcore.dll with the latest version -copy C:\gitroot\AspNetCoreModule\artifacts\build\AspNetCore\bin\Release\x64\aspnetcore.dll "C:\Program Files\IIS Express" -copy C:\gitroot\AspNetCoreModule\artifacts\build\AspNetCore\bin\Release\x64\aspnetcore.pdb "C:\Program Files\IIS Express" - - -# Enable appverif for IISExpress.exe -appverif /verify iisexpress.exe - -# Set the AspNetCoreModuleTest environment variable with the following command -cd C:\gitroot\AspNetCoreModule\test\AspNetCoreModule.Test -dotnet restore -dotnet build -$aspNetCoreModuleTest="C:\gitroot\AspNetCoreModule\test\AspNetCoreModule.Test\bin\Debug\net46" - -if (Test-Path (Join-Path $aspNetCoreModuleTest aspnetcoremodule.test.dll)) -{ - # Clean up applicationhost.config of IISExpress - del $env:userprofile\documents\iisexpress\config\applicationhost.config -Confirm:$false -Force - Start-Process "C:\Program Files\IIS Express\iisexpress.exe" - Sleep 3 - Stop-Process -Name iisexpress - - # Create sites - (1..50) | foreach { md ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) 2> out-null } - (1..50) | foreach { copy C:\gitroot\AspNetCoreModule\test\StressTestWebRoot\web.config ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) } - (1..50) | foreach { - $path = ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) - $appPath = "/foo"+$_ - & "C:\Program Files\IIS Express\appcmd.exe" add app /site.name:"WebSite1" /path:$appPath /physicalPath:$path - } - - <#(1..50) | foreach { - $configpath = ("WebSite1/foo" + $_) - $value = "C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ + ".exe" - & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /processPath:$value - } - (1..50) | foreach { copy C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo.exe ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ +".exe") } - (1..50) | foreach { - $configpath = ("WebSite1/foo" + $_) - $value = "%AspNetCoreModuleTest%\AspnetCoreApp_HelloWeb\foo" + $_ + ".exe" - & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /processPath:$value /apphostconfig:%AspNetCoreModuleTest%\config\applicationhost.config - - $value = "%AspNetCoreModuleTest%\AspnetCoreApp_HelloWeb\AutobahnTestServer.dll" - & "C:\Program Files\IIS Express\appcmd.exe" set config $configpath -section:system.webServer/aspNetCore /arguments:$value /apphostconfig:%AspNetCoreModuleTest%\config\applicationhost.config - } - #> - - # Start IISExpress with running the below command - &"C:\Program Files\Debugging Tools for Windows (x64)\windbg.exe" /g /G "C:\Program Files\IIS Express\iisexpress.exe" - - - # 6. Start stress testing - (1..10000) | foreach { - if ($_ % 2 -eq 0) - { - ("Recycling backend only") - stop-process -name dotnet - (1..50) | foreach { del ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ + "\app_offline.htm") -confirm:$false -Force 2> out-null } - stop-process -name dotnet - } - else - { - ("Recycling backedn + enabling appoffline ....") - stop-process -name dotnet - (1..50) | foreach { copy C:\gitroot\AspNetCoreModule\test\StressTestWebRoot\app_offline.htm ("C:\inetpub\wwwroot\AspnetCoreHandler_HelloWeb\foo" + $_ ) } - } - Sleep 1 - - (1..10) | foreach { - (1..50) | foreach { - invoke-webrequest ("http://localhost:8080/foo"+$_) > $null - } - } - } - - - # Stress test idea - # 1. Use Web Stress Tester - # 2. Run stop-process -name dotnet - # 3. Hit Q command to IISExpress console window - # 4. Use app_offline.htm - # 5. Save dummy web.config -} - -// bp aspnetcore!FORWARDING_HANDLER::FORWARDING_HANDLER -// bp aspnetcore!FORWARDING_HANDLER::~FORWARDING_HANDLER \ No newline at end of file diff --git a/src/IISIntegration/tools/update_schema.ps1 b/src/IISIntegration/tools/update_schema.ps1 index b299c91e6a..2a45a3152c 100644 --- a/src/IISIntegration/tools/update_schema.ps1 +++ b/src/IISIntegration/tools/update_schema.ps1 @@ -1,6 +1,7 @@ <# .DESCRIPTION Updates aspnetcore_schema.xml to the latest version. +Updates aspnetcore_schema.xml to the latest version. Requires admin privileges. #> [cmdletbinding(SupportsShouldProcess = $true)] @@ -9,7 +10,13 @@ param() $ErrorActionPreference = 'Stop' Set-StrictMode -Version 1 -$schemaSource = Resolve-Path "$PSScriptRoot\..\src\AspNetCoreModuleV2\AspNetCore\aspnetcore_schema.xml" +$ancmSchemaFiles = @( + "aspnetcore_schema.xml", + "aspnetcore_schema_v2.xml" +) + +$ancmSchemaFileLocation = Resolve-Path "$PSScriptRoot\..\src\AspNetCoreModuleV2\AspNetCore\aspnetcore_schema_v2.xml"; + [bool]$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if (-not $isAdmin -and -not $WhatIfPreference) { @@ -33,23 +40,30 @@ if (-not $isAdmin -and -not $WhatIfPreference) { } } -$destinations = @( - "${env:ProgramFiles(x86)}\IIS Express\config\schema\aspnetcore_schema.xml", - "${env:ProgramFiles}\IIS Express\config\schema\aspnetcore_schema.xml", - "${env:windir}\system32\inetsrv\config\schema\aspnetcore_schema.xml" -) | Get-Unique +for ($i=0; $i -lt $ancmSchemaFiles.Length; $i++) +{ + $schemaFile = $ancmSchemaFiles[$i] + $schemaSource = $ancmSchemaFileLocation + $destinations = @( + "${env:ProgramFiles(x86)}\IIS Express\config\schema\", + "${env:ProgramFiles}\IIS Express\config\schema\", + "${env:windir}\system32\inetsrv\config\schema\" + ) -foreach ($dest in $destinations) { - if (-not (Test-Path $dest)) { - Write-Host -ForegroundColor Yellow "Skipping $dest. File does not already exist." - continue - } + foreach ($destPath in $destinations) { + $dest = "$destPath\${schemaFile}"; - if ($PSCmdlet.ShouldProcess($dest, "Replace file")) { - Write-Host "Updated $dest" - Move-Item $dest "${dest}.bak" -ErrorAction Ignore - Copy-Item $schemaSource $dest + if (!(Test-Path $destPath)) + { + Write-Host "$destPath doesn't exist" + continue; + } + + if ($PSCmdlet.ShouldProcess($dest, "Replace file")) { + Write-Host "Updated $dest" + Move-Item $dest "${dest}.bak" -ErrorAction Ignore + Copy-Item $schemaSource $dest + } } } - diff --git a/src/IISIntegration/version.props b/src/IISIntegration/version.props index e897351a24..adb9762301 100644 --- a/src/IISIntegration/version.props +++ b/src/IISIntegration/version.props @@ -1,18 +1,20 @@  - 2 - 1 - 2 + 3 + 0 + 0 $(DotNetMajorVersion).$(DotNetMinorVersion).$(DotNetPatchVersion) - 12 + 13 $(DotNetMinorVersion) $(DotNetPatchVersion) - rtm + alpha1 + ancm-oob $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 a- $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) $(VersionSuffix)-$(BuildNumber) + 2.0.0