Merge pull request #1019 from aspnet/merge/release/2.2

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Pavel Krymets 2018-07-06 11:56:23 -07:00 committed by GitHub
commit 690a95dc09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 817 additions and 110 deletions

View File

@ -8,6 +8,7 @@ branches:
install: install:
- ps: .\tools\update_schema.ps1 - ps: .\tools\update_schema.ps1
- git submodule update --init --recursive - git submodule update --init --recursive
- net start w3svc
build_script: build_script:
- ps: .\run.ps1 default-build - ps: .\run.ps1 default-build
clone_depth: 1 clone_depth: 1

View File

@ -6,9 +6,9 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using IISIntegration.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Performance namespace Microsoft.AspNetCore.Server.IIS.Performance

View File

@ -1,4 +1,4 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup> </PropertyGroup>
@ -38,8 +38,10 @@
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion> <MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>3.0.0-alpha1-10016</MicrosoftNetHttpHeadersPackageVersion> <MicrosoftNetHttpHeadersPackageVersion>3.0.0-alpha1-10016</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion> <MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MicrosoftWebAdministrationPackageVersion>11.1.0</MicrosoftWebAdministrationPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion> <NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion> <SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion>
<SystemDiagnosticsEventLogPackageVersion>4.6.0-preview1-26617-01</SystemDiagnosticsEventLogPackageVersion>
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion> <SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion>
<SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion> <SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion>
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion> <SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>

View File

@ -20,9 +20,11 @@
typedef typedef
HRESULT HRESULT
(WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)( (WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)(
_In_ IHttpServer *pServer, _In_ IHttpServer *pServer,
_In_ IHttpApplication *pHttpApplication, _In_ IHttpApplication *pHttpApplication,
_Out_ IAPPLICATION **pApplication _In_ APPLICATION_PARAMETER *pParameters,
_In_ DWORD nParameters,
_Out_ IAPPLICATION **pApplication
); );
extern BOOL g_fRecycleProcessCalled; extern BOOL g_fRecycleProcessCalled;

View File

@ -38,12 +38,6 @@ public:
return &m_struApplicationPhysicalPath; return &m_struApplicationPhysicalPath;
} }
STRU*
QueryApplicationPath()
{
return &m_struApplication;
}
STRU* STRU*
QueryConfigPath() QueryConfigPath()
{ {
@ -83,7 +77,6 @@ private:
STRU m_struArguments; STRU m_struArguments;
STRU m_struProcessPath; STRU m_struProcessPath;
STRU m_struApplication;
STRU m_struApplicationPhysicalPath; STRU m_struApplicationPhysicalPath;
STRU m_struConfigPath; STRU m_struConfigPath;
APP_HOSTING_MODEL m_hostingModel; APP_HOSTING_MODEL m_hostingModel;

View File

@ -3,6 +3,7 @@
#include "applicationinfo.h" #include "applicationinfo.h"
#include <array>
#include "proxymodule.h" #include "proxymodule.h"
#include "hostfxr_utility.h" #include "hostfxr_utility.h"
#include "utility.h" #include "utility.h"
@ -152,7 +153,7 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle()
STACK_STRU(strEventMsg, 256); STACK_STRU(strEventMsg, 256);
if (SUCCEEDED(strEventMsg.SafeSnwprintf( if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG,
m_pConfiguration->QueryApplicationPath()->QueryStr()))) m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr())))
{ {
UTILITY::LogEvent(g_hEventLog, UTILITY::LogEvent(g_hEventLog,
EVENTLOG_INFORMATION_TYPE, EVENTLOG_INFORMATION_TYPE,
@ -222,8 +223,16 @@ APPLICATION_INFO::EnsureApplicationCreated(
FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION)); FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION));
} }
FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication(m_pServer, pHttpContext->GetApplication(), &pApplication)); std::array<APPLICATION_PARAMETER, 1> parameters {
pApplication->SetParameter(L"InProcessExeLocation", struExeLocation.QueryStr()); {"InProcessExeLocation", struExeLocation.QueryStr()}
};
FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication(
m_pServer,
pHttpContext->GetApplication(),
parameters.data(),
static_cast<DWORD>(parameters.size()),
&pApplication));
m_pApplication = pApplication; m_pApplication = pApplication;
} }

View File

@ -56,7 +56,10 @@ ASPNETCORE_SHIM_CONFIG::Populate(
CS_ASPNETCORE_PROCESS_ARGUMENTS, CS_ASPNETCORE_PROCESS_ARGUMENTS,
&m_struArguments)); &m_struArguments));
RETURN_IF_FAILED(ConfigUtility::FindHandlerVersion(pAspNetCoreElement, &m_struHandlerVersion)); if (m_hostingModel == HOSTING_OUT_PROCESS)
{
RETURN_IF_FAILED(ConfigUtility::FindHandlerVersion(pAspNetCoreElement, &m_struHandlerVersion));
}
return S_OK; return S_OK;
} }

View File

@ -18,7 +18,7 @@ Environment::ExpandEnvironmentVariables(const std::wstring & str)
do do
{ {
expandedStr.resize(requestedSize); expandedStr.resize(requestedSize);
requestedSize = ExpandEnvironmentStringsW(str.c_str(), &expandedStr[0], requestedSize); requestedSize = ExpandEnvironmentStringsW(str.c_str(), expandedStr.data(), requestedSize);
if (requestedSize == 0) if (requestedSize == 0)
{ {
throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables"); throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables");
@ -30,3 +30,31 @@ Environment::ExpandEnvironmentVariables(const std::wstring & str)
return expandedStr; return expandedStr;
} }
std::optional<std::wstring>
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);
return expandedStr;
}

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <optional>
class Environment class Environment
{ {
@ -13,5 +14,7 @@ public:
static static
std::wstring ExpandEnvironmentVariables(const std::wstring & str); std::wstring ExpandEnvironmentVariables(const std::wstring & str);
static
std::optional<std::wstring> GetEnvironmentVariableValue(const std::wstring & str);
}; };

View File

@ -40,16 +40,6 @@ public:
} }
} }
VOID
SetParameter(
_In_ LPCWSTR pzName,
_In_ LPCWSTR pzValue)
override
{
UNREFERENCED_PARAMETER(pzName);
UNREFERENCED_PARAMETER(pzValue);
}
protected: protected:
volatile APPLICATION_STATUS m_status = APPLICATION_STATUS::UNKNOWN; volatile APPLICATION_STATUS m_status = APPLICATION_STATUS::UNKNOWN;

View File

@ -28,7 +28,12 @@ public:
STRU strHandlerName; STRU strHandlerName;
STRU strHandlerValue; STRU strHandlerValue;
RETURN_IF_FAILED(GetElementChildByName(pElement, CS_ASPNETCORE_HANDLER_SETTINGS,&pHandlerSettings)); // 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(pHandlerSettings->get_Collection(&pHandlerSettingsCollection));
RETURN_IF_FAILED(hr = FindFirstElement(pHandlerSettingsCollection, &index, &pHandlerVar)); RETURN_IF_FAILED(hr = FindFirstElement(pHandlerSettingsCollection, &index, &pHandlerVar));

View File

@ -53,7 +53,7 @@ DebugInitialize()
try try
{ {
const auto value = std::stoi(Environment::ExpandEnvironmentVariables(L"%ASPNETCORE_MODULE_DEBUG%")); const auto value = std::stoi(Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG").value_or(L"0"));
if (value >= 1) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_ERROR; if (value >= 1) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_ERROR;
if (value >= 2) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_WARNING; if (value >= 2) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_WARNING;
@ -67,19 +67,18 @@ DebugInitialize()
try try
{ {
const auto debugOutputFile = Environment::ExpandEnvironmentVariables(L"%ASPNETCORE_MODULE_DEBUG_FILE%"); const auto debugOutputFile = Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG_FILE");
if (!debugOutputFile.empty()) if (debugOutputFile.has_value())
{ {
g_logFile = CreateFileW(debugOutputFile.c_str(), g_logFile = CreateFileW(debugOutputFile.value().c_str(),
FILE_GENERIC_WRITE, (GENERIC_READ | GENERIC_WRITE),
FILE_SHARE_READ | FILE_SHARE_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
nullptr, nullptr,
OPEN_ALWAYS, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
nullptr nullptr
); );
if (g_logFile != INVALID_HANDLE_VALUE) if (g_logFile != INVALID_HANDLE_VALUE)
{ {
InitializeSRWLock(&g_logFileLock); InitializeSRWLock(&g_logFileLock);
@ -145,6 +144,7 @@ DebugPrint(
SetFilePointer(g_logFile, 0, nullptr, FILE_END); SetFilePointer(g_logFile, 0, nullptr, FILE_END);
WriteFile(g_logFile, strOutput.QueryStr(), strOutput.QueryCB(), &nBytesWritten, nullptr); WriteFile(g_logFile, strOutput.QueryStr(), strOutput.QueryCB(), &nBytesWritten, nullptr);
FlushFileBuffers(g_logFile);
} }
} }
} }

View File

@ -128,11 +128,6 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
fs::path processPath = Environment::ExpandEnvironmentVariables(pcwzProcessPath); fs::path processPath = Environment::ExpandEnvironmentVariables(pcwzProcessPath);
std::wstring arguments = Environment::ExpandEnvironmentVariables(pcwzArguments); std::wstring arguments = Environment::ExpandEnvironmentVariables(pcwzArguments);
if (processPath.is_relative())
{
processPath = applicationPhysicalPath / processPath;
}
// Check if the absolute path is to dotnet or not. // Check if the absolute path is to dotnet or not.
if (IsDotnetExecutable(processPath)) if (IsDotnetExecutable(processPath))
{ {
@ -143,7 +138,7 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
// Get the absolute path to dotnet. If the path is already an absolute path, it will return that path // Get the absolute path to dotnet. If the path is already an absolute path, it will return that path
// //
// Make sure to append the dotnet.exe path correctly here (pass in regular path)? // Make sure to append the dotnet.exe path correctly here (pass in regular path)?
auto fullProcessPath = GetAbsolutePathToDotnet(processPath); auto fullProcessPath = GetAbsolutePathToDotnet(applicationPhysicalPath, processPath);
if (!fullProcessPath.has_value()) if (!fullProcessPath.has_value())
{ {
return E_FAIL; return E_FAIL;
@ -172,6 +167,11 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
{ {
WLOG_INFOF(L"Process path %s is not dotnet, treating application as standalone", processPath.c_str()); WLOG_INFOF(L"Process path %s is not dotnet, treating application as standalone", processPath.c_str());
if (processPath.is_relative())
{
processPath = applicationPhysicalPath / processPath;
}
// //
// The processPath is a path to the application executable // The processPath is a path to the application executable
// like: C:\test\MyApp.Exe or MyApp.Exe // like: C:\test\MyApp.Exe or MyApp.Exe
@ -331,10 +331,17 @@ Finished:
std::optional<fs::path> std::optional<fs::path>
HOSTFXR_UTILITY::GetAbsolutePathToDotnet( HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
const fs::path & applicationPath,
const fs::path & requestedPath const fs::path & requestedPath
) )
{ {
WLOG_INFOF(L"Resolving absolute path do dotnet.exe from %s", requestedPath.c_str()); WLOG_INFOF(L"Resolving absolute path to dotnet.exe from %s", 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 we are given an absolute path to dotnet.exe, we are done
@ -360,7 +367,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
// If we encounter any failures, try getting dotnet.exe from the // If we encounter any failures, try getting dotnet.exe from the
// backup location. // backup location.
// Only do it if no path is specified // Only do it if no path is specified
if (!requestedPath.has_parent_path()) if (requestedPath.has_parent_path())
{ {
return std::nullopt; return std::nullopt;
} }
@ -368,7 +375,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
const auto dotnetViaWhere = InvokeWhereToFindDotnet(); const auto dotnetViaWhere = InvokeWhereToFindDotnet();
if (dotnetViaWhere.has_value()) if (dotnetViaWhere.has_value())
{ {
WLOG_INFOF(L"Found dotnet.exe wia where.exe invocation at %s", dotnetViaWhere.value().c_str()); WLOG_INFOF(L"Found dotnet.exe via where.exe invocation at %s", dotnetViaWhere.value().c_str());
return dotnetViaWhere; return dotnetViaWhere;
} }
@ -394,7 +401,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
std::vector<std::wstring> versionFolders; std::vector<std::wstring> versionFolders;
const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr"; const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr";
WLOG_INFOF(L"Resolving absolute path do hostfxr.dll from %s", dotnetPath.c_str()); WLOG_INFOF(L"Resolving absolute path to hostfxr.dll from %s", dotnetPath.c_str());
if (!is_directory(hostFxrBase)) if (!is_directory(hostFxrBase))
{ {

View File

@ -91,6 +91,7 @@ public:
static static
std::optional<std::experimental::filesystem::path> std::optional<std::experimental::filesystem::path>
GetAbsolutePathToDotnet( GetAbsolutePathToDotnet(
_In_ const std::experimental::filesystem::path & applicationPath,
_In_ const std::experimental::filesystem::path & requestedPath _In_ const std::experimental::filesystem::path & requestedPath
); );
}; };

View File

@ -14,6 +14,12 @@ enum APPLICATION_STATUS
FAIL FAIL
}; };
struct APPLICATION_PARAMETER
{
LPCSTR pzName;
PVOID pValue;
};
class IAPPLICATION class IAPPLICATION
{ {
public: public:
@ -46,10 +52,4 @@ public:
CreateHandler( CreateHandler(
_In_ IHttpContext *pHttpContext, _In_ IHttpContext *pHttpContext,
_Out_ IREQUEST_HANDLER **pRequestHandler) = 0; _Out_ IREQUEST_HANDLER **pRequestHandler) = 0;
virtual
VOID
SetParameter(
_In_ LPCWSTR pzName,
_In_ LPCWSTR pzValue) = 0;
}; };

View File

@ -8,8 +8,8 @@
#define IDS_INVALID_PROPERTY 1000 #define IDS_INVALID_PROPERTY 1000
#define IDS_SERVER_ERROR 1001 #define IDS_SERVER_ERROR 1001
#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" #define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module V2"
#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" #define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module V2"
#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 #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_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and process '%d' is listening on port '%d'."

View File

@ -572,13 +572,7 @@ UTILITY::LogEvent(
); );
} }
STACK_STRA(converted, 256); WDebugPrintf(dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, L"Event Log: %s", pstrMsg);
if (converted.CopyW(pstrMsg))
{
DebugPrintf(
dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO,
"Event Log: %s", converted.QueryStr());
}
} }
VOID VOID

View File

@ -82,9 +82,11 @@ BOOL APIENTRY DllMain(HMODULE hModule,
HRESULT HRESULT
__stdcall __stdcall
CreateApplication( CreateApplication(
_In_ IHttpServer *pServer, _In_ IHttpServer *pServer,
_In_ IHttpApplication *pHttpApplication, _In_ IHttpApplication *pHttpApplication,
_Out_ IAPPLICATION **ppApplication _In_ APPLICATION_PARAMETER *pParameters,
_In_ DWORD nParameters,
_Out_ IAPPLICATION **ppApplication
) )
{ {
REQUESTHANDLER_CONFIG *pConfig = NULL; REQUESTHANDLER_CONFIG *pConfig = NULL;
@ -97,11 +99,11 @@ CreateApplication(
auto config = std::unique_ptr<REQUESTHANDLER_CONFIG>(pConfig); auto config = std::unique_ptr<REQUESTHANDLER_CONFIG>(pConfig);
BOOL disableStartupPage = pConfig->QueryDisableStartUpErrorPage(); const bool disableStartupPage = pConfig->QueryDisableStartUpErrorPage();
auto pApplication = std::make_unique<IN_PROCESS_APPLICATION>(pServer, std::move(config)); auto pApplication = std::make_unique<IN_PROCESS_APPLICATION>(pServer, std::move(config), pParameters, nParameters);
if (FAILED(pApplication->LoadManagedApplication())) if (FAILED_LOG(pApplication->LoadManagedApplication()))
{ {
// Set the currently running application to a fake application that returns startup exceptions. // Set the currently running application to a fake application that returns startup exceptions.
*ppApplication = new StartupExceptionApplication(pServer, disableStartupPage); *ppApplication = new StartupExceptionApplication(pServer, disableStartupPage);

View File

@ -13,18 +13,22 @@
#include "exceptions.h" #include "exceptions.h"
#include "LoggingHelpers.h" #include "LoggingHelpers.h"
const LPCSTR IN_PROCESS_APPLICATION::s_exeLocationParameterName = "InProcessExeLocation";
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
IHttpServer *pHttpServer, IHttpServer *pHttpServer,
std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig) : std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig,
APPLICATION_PARAMETER *pParameters,
DWORD nParameters) :
InProcessApplicationBase(pHttpServer),
m_pHttpServer(pHttpServer), m_pHttpServer(pHttpServer),
m_ProcessExitCode(0), m_ProcessExitCode(0),
m_fBlockCallbacksIntoManaged(FALSE), m_fBlockCallbacksIntoManaged(FALSE),
m_fInitialized(FALSE),
m_fShutdownCalledFromNative(FALSE), m_fShutdownCalledFromNative(FALSE),
m_fShutdownCalledFromManaged(FALSE), m_fShutdownCalledFromManaged(FALSE),
InProcessApplicationBase(pHttpServer), m_fInitialized(FALSE),
m_pConfig(std::move(pConfig)) m_pConfig(std::move(pConfig))
{ {
// is it guaranteed that we have already checked app offline at this point? // is it guaranteed that we have already checked app offline at this point?
@ -32,6 +36,13 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
DBG_ASSERT(pHttpServer != NULL); DBG_ASSERT(pHttpServer != NULL);
DBG_ASSERT(pConfig != NULL); DBG_ASSERT(pConfig != NULL);
for (DWORD i = 0; i < nParameters; i++)
{
if (_stricmp(pParameters[i].pzName, s_exeLocationParameterName) == 0)
{
m_struExeLocation.Copy(reinterpret_cast<PCWSTR>(pParameters[i].pValue));
}
}
m_status = APPLICATION_STATUS::STARTING; m_status = APPLICATION_STATUS::STARTING;
} }

View File

@ -18,7 +18,9 @@ class IN_PROCESS_APPLICATION : public InProcessApplicationBase
public: public:
IN_PROCESS_APPLICATION( IN_PROCESS_APPLICATION(
IHttpServer* pHttpServer, IHttpServer* pHttpServer,
std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig); std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig,
APPLICATION_PARAMETER *pParameters,
DWORD nParameters);
~IN_PROCESS_APPLICATION(); ~IN_PROCESS_APPLICATION();
@ -42,19 +44,6 @@ public:
_Out_ IREQUEST_HANDLER **pRequestHandler) _Out_ IREQUEST_HANDLER **pRequestHandler)
override; override;
VOID
SetParameter(
_In_ LPCWSTR pzName,
_In_ LPCWSTR pzValue)
override
{
const auto exeLocationParameterName = L"InProcessExeLocation";
if (_wcsicmp(pzName, exeLocationParameterName) == 0)
{
m_struExeLocation.Copy(pzValue);
}
}
// Executes the .NET Core process // Executes the .NET Core process
HRESULT HRESULT
ExecuteApplication( ExecuteApplication(
@ -162,7 +151,7 @@ private:
IOutputManager* m_pLoggerProvider; IOutputManager* m_pLoggerProvider;
std::unique_ptr<REQUESTHANDLER_CONFIG> m_pConfig; std::unique_ptr<REQUESTHANDLER_CONFIG> m_pConfig;
static const LPCSTR s_exeLocationParameterName;
static static
VOID VOID

View File

@ -272,9 +272,13 @@ __stdcall
CreateApplication( CreateApplication(
_In_ IHttpServer *pServer, _In_ IHttpServer *pServer,
_In_ IHttpApplication *pHttpApplication, _In_ IHttpApplication *pHttpApplication,
_In_ APPLICATION_PARAMETER *pParameters,
_In_ DWORD nParameters,
_Out_ IAPPLICATION **ppApplication _Out_ IAPPLICATION **ppApplication
) )
{ {
UNREFERENCED_PARAMETER(pParameters);
UNREFERENCED_PARAMETER(nParameters);
HRESULT hr = S_OK; HRESULT hr = S_OK;
IAPPLICATION *pApplication = NULL; IAPPLICATION *pApplication = NULL;
REQUESTHANDLER_CONFIG *pConfig = NULL; REQUESTHANDLER_CONFIG *pConfig = NULL;

View File

@ -13,7 +13,7 @@ PipeOutputManager::PipeOutputManager() :
m_dwStdErrReadTotal(0), m_dwStdErrReadTotal(0),
m_hErrReadPipe(INVALID_HANDLE_VALUE), m_hErrReadPipe(INVALID_HANDLE_VALUE),
m_hErrWritePipe(INVALID_HANDLE_VALUE), m_hErrWritePipe(INVALID_HANDLE_VALUE),
m_hErrThread(INVALID_HANDLE_VALUE), m_hErrThread(NULL),
m_fDisposed(FALSE) m_fDisposed(FALSE)
{ {
InitializeSRWLock(&m_srwLock); InitializeSRWLock(&m_srwLock);
@ -54,7 +54,6 @@ PipeOutputManager::StopOutputRedirection()
if (m_fdPreviousStdOut >= 0) if (m_fdPreviousStdOut >= 0)
{ {
_dup2(m_fdPreviousStdOut, _fileno(stdout));
LOG_IF_DUPFAIL(_dup2(m_fdPreviousStdOut, _fileno(stdout))); LOG_IF_DUPFAIL(_dup2(m_fdPreviousStdOut, _fileno(stdout)));
} }
else else
@ -79,7 +78,6 @@ PipeOutputManager::StopOutputRedirection()
// GetExitCodeThread returns 0 on failure; thread status code is invalid. // GetExitCodeThread returns 0 on failure; thread status code is invalid.
if (m_hErrThread != NULL && if (m_hErrThread != NULL &&
m_hErrThread != INVALID_HANDLE_VALUE &&
!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) && !LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) &&
dwThreadStatus == STILL_ACTIVE) dwThreadStatus == STILL_ACTIVE)
{ {
@ -96,8 +94,12 @@ PipeOutputManager::StopOutputRedirection()
} }
} }
CloseHandle(m_hErrThread); if (m_hErrThread != NULL)
m_hErrThread = NULL; {
CloseHandle(m_hErrThread);
m_hErrThread = NULL;
}
if (m_hErrReadPipe != INVALID_HANDLE_VALUE) if (m_hErrReadPipe != INVALID_HANDLE_VALUE)
{ {

View File

@ -89,4 +89,18 @@ namespace ConfigUtilityTests
EXPECT_EQ(hr, S_OK); EXPECT_EQ(hr, S_OK);
EXPECT_STREQ(handlerVersion.QueryStr(), L"value2"); EXPECT_STREQ(handlerVersion.QueryStr(), L"value2");
} }
TEST(ConfigUtilityTestSingle, IgnoresFailedGetElement)
{
STRU handlerVersion;
auto element = std::make_unique<NiceMock<MockElement>>();
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"");
}
} }

View File

@ -32,6 +32,26 @@ namespace PipeOutputManagerTests
pManager->NotifyStartupComplete(); pManager->NotifyStartupComplete();
}
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();
ASSERT_EQ(S_OK, pManager->Start());
pManager->NotifyStartupComplete();
_dup2(m_fdPreviousStdOut, _fileno(stdout));
_dup2(m_fdPreviousStdErr, _fileno(stderr));
// Test will fail if we didn't redirect stdout back to a file descriptor. // 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. // 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. // If the output still points to a file/pipe, the test (and all other tests after it) will fail.

View File

@ -3,6 +3,7 @@
#include "stdafx.h" #include "stdafx.h"
#include <array>
#include "inprocessapplication.h" #include "inprocessapplication.h"
#include "fakeclasses.h" #include "fakeclasses.h"
@ -17,11 +18,13 @@ namespace InprocessTests
auto server = new MockHttpServer(); auto server = new MockHttpServer();
auto requestHandlerConfig = MockRequestHandlerConfig::CreateConfig(); auto requestHandlerConfig = MockRequestHandlerConfig::CreateConfig();
auto config = std::unique_ptr<REQUESTHANDLER_CONFIG>(requestHandlerConfig); auto config = std::unique_ptr<REQUESTHANDLER_CONFIG>(requestHandlerConfig);
IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, std::move(config));
{ std::wstring exePath(L"hello");
std::wstring exePath(L"hello"); std::array<APPLICATION_PARAMETER, 1> parameters {
app->SetParameter(L"InProcessExeLocation", exePath.c_str()); {"InProcessExeLocation", exePath.data()}
} };
IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, std::move(config), parameters.data(), 1);
ASSERT_STREQ(app->QueryExeLocation(), L"hello"); ASSERT_STREQ(app->QueryExeLocation(), L"hello");
} }
} }

View File

@ -36,7 +36,9 @@
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Microsoft.Web.Administration" Version="$(MicrosoftWebAdministrationPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="System.Diagnostics.EventLog" Version="$(SystemDiagnosticsEventLogPackageVersion)" />
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" /> <PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" /> <PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" /> <PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />

View File

@ -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.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfIISCannotRun]
public class IISTests : FunctionalTestsBase
{
[ConditionalFact]
public Task HelloWorld_IIS_CoreClr_X64_Standalone()
{
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Standalone);
}
[ConditionalFact]
public Task HelloWorld_IIS_CoreClr_X64_Portable()
{
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
}
private async Task HelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType)
{
var deploymentParameters = Helpers.GetBaseDeploymentParameters();
deploymentParameters.ServerType = ServerType.IIS;
deploymentParameters.ApplicationType = applicationType;
var deploymentResult = await DeployAsync(deploymentParameters);
var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld");
var responseText = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello World", responseText);
}
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using IISIntegration.FunctionalTests.Utilities; using IISIntegration.FunctionalTests.Utilities;
@ -32,20 +33,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
deploymentParameters => deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation); deploymentParameters => deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation);
} }
[ConditionalFact] [ConditionalTheory]
public async Task InvalidProcessPath_ExpectServerError() [InlineData("bogus")]
[InlineData("c:\\random files\\dotnet.exe")]
[InlineData(".\\dotnet.exe")]
public async Task InvalidProcessPath_ExpectServerError(string path)
{ {
var dotnetLocation = "bogus";
var deploymentParameters = GetBaseDeploymentParameters(); var deploymentParameters = GetBaseDeploymentParameters();
// Point to dotnet installed in user profile.
deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet.
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path);
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
@ -77,6 +76,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
await AssertStarts( await AssertStarts(
deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path), deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path),
deploymentParameters => deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation)); deploymentParameters => deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation));
// 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")));
} }
private async Task AssertStarts(Action<IISDeploymentResult> postDeploy, Action<DeploymentParameters> preDeploy = null) private async Task AssertStarts(Action<IISDeploymentResult> postDeploy, Action<DeploymentParameters> preDeploy = null)

View File

@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using IISIntegration.FunctionalTests.Utilities; using IISIntegration.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -11,7 +9,7 @@ using Microsoft.Extensions.Logging.Testing;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace IISIntegration.FunctionalTests.Inprocess namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
[SkipIfHostableWebCoreNotAvailible] [SkipIfHostableWebCoreNotAvailible]
public class TestServerTest: LoggedTest public class TestServerTest: LoggedTest

View File

@ -21,10 +21,18 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{ {
if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable)) if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable))
{ {
// enable debug output
parameters.EnvironmentVariables[DebugEnvironmentVariable] = "4"; parameters.EnvironmentVariables[DebugEnvironmentVariable] = "4";
} }
_deployer = ApplicationDeployerFactory.Create(parameters, LoggerFactory);
// Currently hosting throws if the Servertype = IIS.
if (parameters.ServerType == ServerType.IIS)
{
_deployer = new IISDeployer(parameters, LoggerFactory);
}
else
{
_deployer = ApplicationDeployerFactory.Create(parameters, LoggerFactory);
}
var result = await _deployer.DeployAsync(); var result = await _deployer.DeployAsync();

View File

@ -0,0 +1,318 @@
// 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.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
using Microsoft.Web.Administration;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
/// <summary>
/// Represents the IIS website registered in the global applicationHost.config
/// </summary>
internal class IISApplication
{
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(2);
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(100);
private readonly ServerManager _serverManager = new ServerManager();
private readonly DeploymentParameters _deploymentParameters;
private readonly ILogger _logger;
private readonly string _ancmVersion;
private readonly object _syncLock = new object();
private readonly string _apphostConfigBackupPath;
private static readonly string _apphostConfigPath = Path.Combine(
Environment.SystemDirectory,
"inetsrv",
"config",
"applicationhost.config");
public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
{
_deploymentParameters = deploymentParameters;
_logger = logger;
_ancmVersion = deploymentParameters.AncmVersion.ToString();
WebSiteName = CreateTestSiteName();
AppPoolName = $"{WebSiteName}Pool";
_apphostConfigBackupPath = Path.Combine(
Environment.SystemDirectory,
"inetsrv",
"config",
$"applicationhost.config.{WebSiteName}backup");
}
public string WebSiteName { get; }
public string AppPoolName { get; }
public async Task StartIIS(Uri uri, string contentRoot)
{
// Backup currently deployed apphost.config file
using (_logger.BeginScope("StartIIS"))
{
AddTemporaryAppHostConfig();
var port = uri.Port;
if (port == 0)
{
throw new NotSupportedException("Cannot set port 0 for IIS.");
}
ConfigureAppPool(contentRoot);
ConfigureSite(contentRoot, port);
ConfigureAppHostConfig(contentRoot);
if (_deploymentParameters.ApplicationType == ApplicationType.Portable)
{
ModifyAspNetCoreSectionInWebConfig("processPath", DotNetMuxer.MuxerPathOrDefault());
}
_serverManager.CommitChanges();
await WaitUntilSiteStarted();
}
}
private void ModifyAspNetCoreSectionInWebConfig(string key, string value)
{
var webConfigFile = Path.Combine(_deploymentParameters.PublishedApplicationRootPath, "web.config");
var config = XDocument.Load(webConfigFile);
var element = config.Descendants("aspNetCore").FirstOrDefault();
element.SetAttributeValue(key, value);
config.Save(webConfigFile);
}
private async Task WaitUntilSiteStarted()
{
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
var site = _serverManager.Sites.FirstOrDefault(s => s.Name.Equals(WebSiteName));
if (site.State == ObjectState.Started)
{
_logger.LogInformation($"Site {WebSiteName} has started.");
return;
}
else if (site.State != ObjectState.Starting)
{
_logger.LogInformation($"Site hasn't started with state: {site.State.ToString()}");
var state = site.Start();
_logger.LogInformation($"Tried to start site, state: {state.ToString()}");
}
}
catch (COMException comException)
{
// Accessing the site.State property while the site
// is starting up returns the COMException
// The object identifier does not represent a valid object.
// (Exception from HRESULT: 0x800710D8)
// This also means the site is not started yet, so catch and retry
// after waiting.
_logger.LogWarning($"ComException: {comException.Message}");
}
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to start site.");
}
public async Task StopAndDeleteAppPool()
{
if (string.IsNullOrEmpty(WebSiteName))
{
return;
}
RestoreAppHostConfig();
_serverManager.CommitChanges();
await WaitUntilSiteStopped();
}
private async Task WaitUntilSiteStopped()
{
var site = _serverManager.Sites.Where(element => element.Name == WebSiteName).FirstOrDefault();
if (site == null)
{
return;
}
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
if (site.State == ObjectState.Stopped)
{
_logger.LogInformation($"Site {WebSiteName} has stopped successfully.");
return;
}
}
catch (COMException)
{
// Accessing the site.State property while the site
// is shutdown down returns the COMException
return;
}
_logger.LogWarning($"IIS has not stopped after {sw.Elapsed.TotalMilliseconds}");
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to stop site {site}.");
}
private void AddTemporaryAppHostConfig()
{
File.Copy(_apphostConfigPath, _apphostConfigBackupPath);
_logger.LogInformation($"Backed up {_apphostConfigPath} to {_apphostConfigBackupPath}");
}
private void RestoreAppHostConfig()
{
RetryHelper.RetryOperation(
() => File.Delete(_apphostConfigPath),
e => _logger.LogWarning($"Failed to delete directory : {e.Message}"),
retryCount: 3,
retryDelayMilliseconds: 100);
File.Move(_apphostConfigBackupPath, _apphostConfigPath);
_logger.LogInformation($"Restored {_apphostConfigPath}.");
}
private ApplicationPool ConfigureAppPool(string contentRoot)
{
var pool = _serverManager.ApplicationPools.Add(AppPoolName);
pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
pool.ManagedRuntimeVersion = string.Empty;
AddEnvironmentVariables(contentRoot, pool);
_logger.LogInformation($"Configured AppPool {AppPoolName}");
return pool;
}
private void AddEnvironmentVariables(string contentRoot, ApplicationPool pool)
{
try
{
var envCollection = pool.GetCollection("environmentVariables");
foreach (var tuple in _deploymentParameters.EnvironmentVariables)
{
AddEnvironmentVariableToAppPool(envCollection, tuple.Key, tuple.Value);
}
}
catch (COMException comException)
{
_logger.LogInformation($"Could not add environment variables to worker process: {comException.Message}");
}
}
private static void AddEnvironmentVariableToAppPool(ConfigurationElementCollection envCollection, string key, string value)
{
var addElement = envCollection.CreateElement("add");
addElement["name"] = key;
addElement["value"] = value;
envCollection.Add(addElement);
}
private Site ConfigureSite(string contentRoot, int port)
{
var site = _serverManager.Sites.Add(WebSiteName, contentRoot, port);
site.Applications.Single().ApplicationPoolName = AppPoolName;
_logger.LogInformation($"Configured Site {WebSiteName} with AppPool {AppPoolName}");
return site;
}
private Configuration ConfigureAppHostConfig(string dllRoot)
{
var config = _serverManager.GetApplicationHostConfiguration();
SetGlobalModuleSection(config, dllRoot);
SetModulesSection(config);
return config;
}
private void SetGlobalModuleSection(Configuration config, string dllRoot)
{
var ancmFile = GetAncmLocation(dllRoot);
var globalModulesSection = config.GetSection("system.webServer/globalModules");
var globalConfigElement = globalModulesSection
.GetCollection()
.Where(element => (string)element["name"] == _ancmVersion)
.FirstOrDefault();
if (globalConfigElement == null)
{
_logger.LogInformation($"Could not find {_ancmVersion} section in global modules; creating section.");
var addElement = globalModulesSection.GetCollection().CreateElement("add");
addElement["name"] = _ancmVersion;
addElement["image"] = ancmFile;
globalModulesSection.GetCollection().Add(addElement);
}
else
{
_logger.LogInformation($"Replacing {_ancmVersion} section in global modules with {ancmFile}");
globalConfigElement["image"] = ancmFile;
}
}
private void SetModulesSection(Configuration config)
{
var modulesSection = config.GetSection("system.webServer/modules");
var moduleConfigElement = modulesSection.GetCollection().Where(element => (string)element["name"] == _ancmVersion).FirstOrDefault();
if (moduleConfigElement == null)
{
_logger.LogInformation($"Could not find {_ancmVersion} section in modules; creating section.");
var moduleElement = modulesSection.GetCollection().CreateElement("add");
moduleElement["name"] = _ancmVersion;
modulesSection.GetCollection().Add(moduleElement);
}
}
private string CreateTestSiteName()
{
if (!string.IsNullOrEmpty(_deploymentParameters.SiteName))
{
return $"{_deploymentParameters.SiteName}{DateTime.Now.ToString("yyyyMMddHHmmss")}";
}
else
{
return $"testsite{DateTime.Now.ToString("yyyyMMddHHmmss")}";
}
}
private string GetAncmLocation(string dllRoot)
{
var arch = _deploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? @"x64\aspnetcorev2.dll" : @"x86\aspnetcorev2.dll";
var ancmFile = Path.Combine(dllRoot, arch);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
ancmFile = Path.Combine(dllRoot, "aspnetcorev2.dll");
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
}
}
return ancmFile;
}
}
}

View File

@ -0,0 +1,95 @@
// 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 System.Xml.Linq;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
/// <summary>
/// Deployer for IIS.
/// </summary>
public partial class IISDeployer : ApplicationDeployer
{
private IISApplication _application;
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(deploymentParameters, loggerFactory)
{
}
public override void Dispose()
{
if (_application != null)
{
_application.StopAndDeleteAppPool().GetAwaiter().GetResult();
TriggerHostShutdown(_hostShutdownToken);
}
GetLogsFromFile($"{_application.WebSiteName}.txt");
GetLogsFromFile("web.config");
CleanPublishedOutput();
InvokeUserApplicationCleanup();
StopTimer();
}
public override async Task<DeploymentResult> DeployAsync()
{
using (Logger.BeginScope("Deployment"))
{
StartTimer();
var contentRoot = string.Empty;
_application = new IISApplication(DeploymentParameters, Logger);
// For now, only support using published output
DeploymentParameters.PublishApplicationBeforeDeployment = true;
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
DotnetPublish();
contentRoot = DeploymentParameters.PublishedApplicationRootPath;
}
var uri = TestIISUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint);
// To prevent modifying the IIS setup concurrently.
await _application.StartIIS(uri, contentRoot);
// Warm up time for IIS setup.
Logger.LogInformation("Successfully finished IIS application directory setup.");
return new DeploymentResult(
LoggerFactory,
DeploymentParameters,
applicationBaseUri: uri.ToString(),
contentRoot: contentRoot,
hostShutdownToken: _hostShutdownToken.Token
);
}
}
private void GetLogsFromFile(string file)
{
var arr = new string[0];
RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)),
(ex) => Logger.LogError("Could not read log file"),
5,
200);
foreach (var line in arr)
{
Logger.LogInformation(line);
}
}
}
}

View File

@ -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.Net.Http; using System.Net.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -5,7 +5,7 @@ using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
namespace IISIntegration.FunctionalTests.Utilities namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Xml.Linq;
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 SkipIfIISCannotRunAttribute : Attribute, ITestCondition
{
private static readonly bool _isMet;
public static readonly string _skipReason;
public bool IsMet => _isMet;
public string SkipReason => _skipReason;
static SkipIfIISCannotRunAttribute()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_skipReason = "IIS tests can only be run on Windows";
return;
}
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
{
_skipReason += "The current console is not running as admin.";
return;
}
if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe")))
{
_skipReason += "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))
{
_skipReason = "IIS Schema is not installed.";
return;
}
XDocument ancmConfig;
try
{
ancmConfig = XDocument.Load(ancmConfigPath);
}
catch
{
_skipReason = "Could not read ANCM schema configuration";
return;
}
_isMet = ancmConfig
.Root
.Descendants("attribute")
.Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal));
_skipReason = _isMet ? null : "IIS schema needs to be upgraded to support ANCM.";
}
}
}

View File

@ -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;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
// Copied from Hosting for now https://github.com/aspnet/Hosting/blob/970bc8a30d66dd6894f8f662e5fdab9e68d57777/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs
internal static class TestIISUriHelper
{
internal static Uri BuildTestUri(ServerType serverType)
{
return BuildTestUri(serverType, hint: null);
}
internal static Uri BuildTestUri(ServerType serverType, string hint)
{
// Assume status messages are enabled for Kestrel and disabled for all other servers.
return BuildTestUri(serverType, hint, statusMessagesEnabled: serverType == ServerType.Kestrel);
}
internal static Uri BuildTestUri(ServerType serverType, string hint, bool statusMessagesEnabled)
{
if (string.IsNullOrEmpty(hint))
{
if (serverType == ServerType.Kestrel && statusMessagesEnabled)
{
// Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape
// the assigned port from the status message, which should be 100% reliable since the port is bound
// once and never released. Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
// supported, so the port is only bound on "127.0.0.1" (IPv4). If a test explicitly requires IPv6,
// it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
return new UriBuilder("http", "127.0.0.1", 0).Uri;
}
else
{
// If the server type is not Kestrel, or status messages are disabled, there is no status message
// from which to scrape the assigned port, so the less reliable GetNextPort() must be used. The
// port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific
// (non-zero) port.
return new UriBuilder("http", "localhost", GetNextPort()).Uri;
}
}
else
{
var uriHint = new Uri(hint);
if (uriHint.Port == 0)
{
// Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity.
// The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic
// port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath).
return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
}
else
{
// If the hint contains a specific port, return it unchanged.
return uriHint;
}
}
}
// Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
//
// This method is an attempt to safely get a free port from the OS. Most of the time,
// when binding to dynamic port "0" the OS increments the assigned port, so it's safe
// to re-use the assigned port in another process. However, occasionally the OS will reuse
// a recently assigned port instead of incrementing, which causes flaky tests with AddressInUse
// exceptions. This method should only be used when the application itself cannot use
// dynamic port "0" (e.g. IISExpress). Most functional tests using raw Kestrel
// (with status messages enabled) should directly bind to dynamic port "0" and scrape
// the assigned port from the status message, which should be 100% reliable since the port
// is bound once and never released.
internal static int GetNextPort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}
}
}

View File

@ -15,7 +15,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace IISIntegration.FunctionalTests.Utilities namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
public class TestServer: IDisposable, IStartup public class TestServer: IDisposable, IStartup
{ {