Merge pull request #1019 from aspnet/merge/release/2.2
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
690a95dc09
|
|
@ -8,6 +8,7 @@ branches:
|
|||
install:
|
||||
- ps: .\tools\update_schema.ps1
|
||||
- git submodule update --init --recursive
|
||||
- net start w3svc
|
||||
build_script:
|
||||
- ps: .\run.ps1 default-build
|
||||
clone_depth: 1
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using IISIntegration.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IIS.Performance
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
|
@ -38,8 +38,10 @@
|
|||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
||||
<MicrosoftNetHttpHeadersPackageVersion>3.0.0-alpha1-10016</MicrosoftNetHttpHeadersPackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MicrosoftWebAdministrationPackageVersion>11.1.0</MicrosoftWebAdministrationPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion>
|
||||
<SystemDiagnosticsEventLogPackageVersion>4.6.0-preview1-26617-01</SystemDiagnosticsEventLogPackageVersion>
|
||||
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion>
|
||||
<SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion>
|
||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@
|
|||
typedef
|
||||
HRESULT
|
||||
(WINAPI * PFN_ASPNETCORE_CREATE_APPLICATION)(
|
||||
_In_ IHttpServer *pServer,
|
||||
_In_ IHttpApplication *pHttpApplication,
|
||||
_Out_ IAPPLICATION **pApplication
|
||||
_In_ IHttpServer *pServer,
|
||||
_In_ IHttpApplication *pHttpApplication,
|
||||
_In_ APPLICATION_PARAMETER *pParameters,
|
||||
_In_ DWORD nParameters,
|
||||
_Out_ IAPPLICATION **pApplication
|
||||
);
|
||||
|
||||
extern BOOL g_fRecycleProcessCalled;
|
||||
|
|
|
|||
|
|
@ -38,12 +38,6 @@ public:
|
|||
return &m_struApplicationPhysicalPath;
|
||||
}
|
||||
|
||||
STRU*
|
||||
QueryApplicationPath()
|
||||
{
|
||||
return &m_struApplication;
|
||||
}
|
||||
|
||||
STRU*
|
||||
QueryConfigPath()
|
||||
{
|
||||
|
|
@ -83,7 +77,6 @@ private:
|
|||
|
||||
STRU m_struArguments;
|
||||
STRU m_struProcessPath;
|
||||
STRU m_struApplication;
|
||||
STRU m_struApplicationPhysicalPath;
|
||||
STRU m_struConfigPath;
|
||||
APP_HOSTING_MODEL m_hostingModel;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "applicationinfo.h"
|
||||
|
||||
#include <array>
|
||||
#include "proxymodule.h"
|
||||
#include "hostfxr_utility.h"
|
||||
#include "utility.h"
|
||||
|
|
@ -152,7 +153,7 @@ APPLICATION_INFO::UpdateAppOfflineFileHandle()
|
|||
STACK_STRU(strEventMsg, 256);
|
||||
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
|
||||
ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG,
|
||||
m_pConfiguration->QueryApplicationPath()->QueryStr())))
|
||||
m_pConfiguration->QueryApplicationPhysicalPath()->QueryStr())))
|
||||
{
|
||||
UTILITY::LogEvent(g_hEventLog,
|
||||
EVENTLOG_INFORMATION_TYPE,
|
||||
|
|
@ -222,8 +223,16 @@ APPLICATION_INFO::EnsureApplicationCreated(
|
|||
FINISHED(HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION));
|
||||
}
|
||||
|
||||
FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication(m_pServer, pHttpContext->GetApplication(), &pApplication));
|
||||
pApplication->SetParameter(L"InProcessExeLocation", struExeLocation.QueryStr());
|
||||
std::array<APPLICATION_PARAMETER, 1> parameters {
|
||||
{"InProcessExeLocation", struExeLocation.QueryStr()}
|
||||
};
|
||||
|
||||
FINISHED_IF_FAILED(m_pfnAspNetCoreCreateApplication(
|
||||
m_pServer,
|
||||
pHttpContext->GetApplication(),
|
||||
parameters.data(),
|
||||
static_cast<DWORD>(parameters.size()),
|
||||
&pApplication));
|
||||
|
||||
m_pApplication = pApplication;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ ASPNETCORE_SHIM_CONFIG::Populate(
|
|||
CS_ASPNETCORE_PROCESS_ARGUMENTS,
|
||||
&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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Environment::ExpandEnvironmentVariables(const std::wstring & str)
|
|||
do
|
||||
{
|
||||
expandedStr.resize(requestedSize);
|
||||
requestedSize = ExpandEnvironmentStringsW(str.c_str(), &expandedStr[0], requestedSize);
|
||||
requestedSize = ExpandEnvironmentStringsW(str.c_str(), expandedStr.data(), requestedSize);
|
||||
if (requestedSize == 0)
|
||||
{
|
||||
throw std::system_error(GetLastError(), std::system_category(), "ExpandEnvironmentVariables");
|
||||
|
|
@ -30,3 +30,31 @@ Environment::ExpandEnvironmentVariables(const std::wstring & str)
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
class Environment
|
||||
{
|
||||
|
|
@ -13,5 +14,7 @@ public:
|
|||
|
||||
static
|
||||
std::wstring ExpandEnvironmentVariables(const std::wstring & str);
|
||||
static
|
||||
std::optional<std::wstring> GetEnvironmentVariableValue(const std::wstring & str);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,16 +40,6 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
VOID
|
||||
SetParameter(
|
||||
_In_ LPCWSTR pzName,
|
||||
_In_ LPCWSTR pzValue)
|
||||
override
|
||||
{
|
||||
UNREFERENCED_PARAMETER(pzName);
|
||||
UNREFERENCED_PARAMETER(pzValue);
|
||||
}
|
||||
|
||||
protected:
|
||||
volatile APPLICATION_STATUS m_status = APPLICATION_STATUS::UNKNOWN;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@ public:
|
|||
STRU strHandlerName;
|
||||
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(hr = FindFirstElement(pHandlerSettingsCollection, &index, &pHandlerVar));
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ DebugInitialize()
|
|||
|
||||
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 >= 2) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_WARNING;
|
||||
|
|
@ -67,19 +67,18 @@ DebugInitialize()
|
|||
|
||||
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(),
|
||||
FILE_GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
g_logFile = CreateFileW(debugOutputFile.value().c_str(),
|
||||
(GENERIC_READ | GENERIC_WRITE),
|
||||
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
|
||||
nullptr,
|
||||
OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (g_logFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
InitializeSRWLock(&g_logFileLock);
|
||||
|
|
@ -145,6 +144,7 @@ DebugPrint(
|
|||
|
||||
SetFilePointer(g_logFile, 0, nullptr, FILE_END);
|
||||
WriteFile(g_logFile, strOutput.QueryStr(), strOutput.QueryCB(), &nBytesWritten, nullptr);
|
||||
FlushFileBuffers(g_logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,11 +128,6 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
|
|||
fs::path processPath = Environment::ExpandEnvironmentVariables(pcwzProcessPath);
|
||||
std::wstring arguments = Environment::ExpandEnvironmentVariables(pcwzArguments);
|
||||
|
||||
if (processPath.is_relative())
|
||||
{
|
||||
processPath = applicationPhysicalPath / processPath;
|
||||
}
|
||||
|
||||
// Check if the absolute path is to dotnet or not.
|
||||
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
|
||||
//
|
||||
// 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())
|
||||
{
|
||||
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());
|
||||
|
||||
if (processPath.is_relative())
|
||||
{
|
||||
processPath = applicationPhysicalPath / processPath;
|
||||
}
|
||||
|
||||
//
|
||||
// The processPath is a path to the application executable
|
||||
// like: C:\test\MyApp.Exe or MyApp.Exe
|
||||
|
|
@ -331,10 +331,17 @@ Finished:
|
|||
|
||||
std::optional<fs::path>
|
||||
HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
||||
const fs::path & applicationPath,
|
||||
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
|
||||
|
|
@ -360,7 +367,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
// If we encounter any failures, try getting dotnet.exe from the
|
||||
// backup location.
|
||||
// Only do it if no path is specified
|
||||
if (!requestedPath.has_parent_path())
|
||||
if (requestedPath.has_parent_path())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
@ -368,7 +375,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
|
||||
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;
|
||||
}
|
||||
|
|
@ -394,7 +401,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
|||
std::vector<std::wstring> versionFolders;
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ public:
|
|||
static
|
||||
std::optional<std::experimental::filesystem::path>
|
||||
GetAbsolutePathToDotnet(
|
||||
_In_ const std::experimental::filesystem::path & applicationPath,
|
||||
_In_ const std::experimental::filesystem::path & requestedPath
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ enum APPLICATION_STATUS
|
|||
FAIL
|
||||
};
|
||||
|
||||
struct APPLICATION_PARAMETER
|
||||
{
|
||||
LPCSTR pzName;
|
||||
PVOID pValue;
|
||||
};
|
||||
|
||||
class IAPPLICATION
|
||||
{
|
||||
public:
|
||||
|
|
@ -46,10 +52,4 @@ public:
|
|||
CreateHandler(
|
||||
_In_ IHttpContext *pHttpContext,
|
||||
_Out_ IREQUEST_HANDLER **pRequestHandler) = 0;
|
||||
|
||||
virtual
|
||||
VOID
|
||||
SetParameter(
|
||||
_In_ LPCWSTR pzName,
|
||||
_In_ LPCWSTR pzValue) = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
#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'."
|
||||
|
|
|
|||
|
|
@ -572,13 +572,7 @@ UTILITY::LogEvent(
|
|||
);
|
||||
}
|
||||
|
||||
STACK_STRA(converted, 256);
|
||||
if (converted.CopyW(pstrMsg))
|
||||
{
|
||||
DebugPrintf(
|
||||
dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO,
|
||||
"Event Log: %s", converted.QueryStr());
|
||||
}
|
||||
WDebugPrintf(dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, L"Event Log: %s", pstrMsg);
|
||||
}
|
||||
|
||||
VOID
|
||||
|
|
|
|||
|
|
@ -82,9 +82,11 @@ BOOL APIENTRY DllMain(HMODULE hModule,
|
|||
HRESULT
|
||||
__stdcall
|
||||
CreateApplication(
|
||||
_In_ IHttpServer *pServer,
|
||||
_In_ IHttpApplication *pHttpApplication,
|
||||
_Out_ IAPPLICATION **ppApplication
|
||||
_In_ IHttpServer *pServer,
|
||||
_In_ IHttpApplication *pHttpApplication,
|
||||
_In_ APPLICATION_PARAMETER *pParameters,
|
||||
_In_ DWORD nParameters,
|
||||
_Out_ IAPPLICATION **ppApplication
|
||||
)
|
||||
{
|
||||
REQUESTHANDLER_CONFIG *pConfig = NULL;
|
||||
|
|
@ -97,11 +99,11 @@ CreateApplication(
|
|||
|
||||
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));
|
||||
|
||||
if (FAILED(pApplication->LoadManagedApplication()))
|
||||
auto pApplication = std::make_unique<IN_PROCESS_APPLICATION>(pServer, std::move(config), pParameters, nParameters);
|
||||
|
||||
if (FAILED_LOG(pApplication->LoadManagedApplication()))
|
||||
{
|
||||
// Set the currently running application to a fake application that returns startup exceptions.
|
||||
*ppApplication = new StartupExceptionApplication(pServer, disableStartupPage);
|
||||
|
|
|
|||
|
|
@ -13,18 +13,22 @@
|
|||
#include "exceptions.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(
|
||||
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_ProcessExitCode(0),
|
||||
m_fBlockCallbacksIntoManaged(FALSE),
|
||||
m_fInitialized(FALSE),
|
||||
m_fShutdownCalledFromNative(FALSE),
|
||||
m_fShutdownCalledFromManaged(FALSE),
|
||||
InProcessApplicationBase(pHttpServer),
|
||||
m_fInitialized(FALSE),
|
||||
m_pConfig(std::move(pConfig))
|
||||
{
|
||||
// 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(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ class IN_PROCESS_APPLICATION : public InProcessApplicationBase
|
|||
public:
|
||||
IN_PROCESS_APPLICATION(
|
||||
IHttpServer* pHttpServer,
|
||||
std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig);
|
||||
std::unique_ptr<REQUESTHANDLER_CONFIG> pConfig,
|
||||
APPLICATION_PARAMETER *pParameters,
|
||||
DWORD nParameters);
|
||||
|
||||
~IN_PROCESS_APPLICATION();
|
||||
|
||||
|
|
@ -42,19 +44,6 @@ public:
|
|||
_Out_ IREQUEST_HANDLER **pRequestHandler)
|
||||
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
|
||||
HRESULT
|
||||
ExecuteApplication(
|
||||
|
|
@ -162,7 +151,7 @@ private:
|
|||
IOutputManager* m_pLoggerProvider;
|
||||
std::unique_ptr<REQUESTHANDLER_CONFIG> m_pConfig;
|
||||
|
||||
|
||||
static const LPCSTR s_exeLocationParameterName;
|
||||
|
||||
static
|
||||
VOID
|
||||
|
|
|
|||
|
|
@ -272,9 +272,13 @@ __stdcall
|
|||
CreateApplication(
|
||||
_In_ IHttpServer *pServer,
|
||||
_In_ IHttpApplication *pHttpApplication,
|
||||
_In_ APPLICATION_PARAMETER *pParameters,
|
||||
_In_ DWORD nParameters,
|
||||
_Out_ IAPPLICATION **ppApplication
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(pParameters);
|
||||
UNREFERENCED_PARAMETER(nParameters);
|
||||
HRESULT hr = S_OK;
|
||||
IAPPLICATION *pApplication = NULL;
|
||||
REQUESTHANDLER_CONFIG *pConfig = NULL;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ PipeOutputManager::PipeOutputManager() :
|
|||
m_dwStdErrReadTotal(0),
|
||||
m_hErrReadPipe(INVALID_HANDLE_VALUE),
|
||||
m_hErrWritePipe(INVALID_HANDLE_VALUE),
|
||||
m_hErrThread(INVALID_HANDLE_VALUE),
|
||||
m_hErrThread(NULL),
|
||||
m_fDisposed(FALSE)
|
||||
{
|
||||
InitializeSRWLock(&m_srwLock);
|
||||
|
|
@ -54,7 +54,6 @@ PipeOutputManager::StopOutputRedirection()
|
|||
|
||||
if (m_fdPreviousStdOut >= 0)
|
||||
{
|
||||
_dup2(m_fdPreviousStdOut, _fileno(stdout));
|
||||
LOG_IF_DUPFAIL(_dup2(m_fdPreviousStdOut, _fileno(stdout)));
|
||||
}
|
||||
else
|
||||
|
|
@ -79,7 +78,6 @@ PipeOutputManager::StopOutputRedirection()
|
|||
|
||||
// GetExitCodeThread returns 0 on failure; thread status code is invalid.
|
||||
if (m_hErrThread != NULL &&
|
||||
m_hErrThread != INVALID_HANDLE_VALUE &&
|
||||
!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) &&
|
||||
dwThreadStatus == STILL_ACTIVE)
|
||||
{
|
||||
|
|
@ -96,8 +94,12 @@ PipeOutputManager::StopOutputRedirection()
|
|||
}
|
||||
}
|
||||
|
||||
CloseHandle(m_hErrThread);
|
||||
m_hErrThread = NULL;
|
||||
if (m_hErrThread != NULL)
|
||||
{
|
||||
CloseHandle(m_hErrThread);
|
||||
m_hErrThread = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (m_hErrReadPipe != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -89,4 +89,18 @@ namespace ConfigUtilityTests
|
|||
EXPECT_EQ(hr, S_OK);
|
||||
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"");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,26 @@ namespace PipeOutputManagerTests
|
|||
|
||||
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.
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <array>
|
||||
#include "inprocessapplication.h"
|
||||
#include "fakeclasses.h"
|
||||
|
||||
|
|
@ -17,11 +18,13 @@ namespace InprocessTests
|
|||
auto server = new MockHttpServer();
|
||||
auto requestHandlerConfig = MockRequestHandlerConfig::CreateConfig();
|
||||
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");
|
||||
app->SetParameter(L"InProcessExeLocation", exePath.c_str());
|
||||
}
|
||||
|
||||
std::wstring exePath(L"hello");
|
||||
std::array<APPLICATION_PARAMETER, 1> parameters {
|
||||
{"InProcessExeLocation", exePath.data()}
|
||||
};
|
||||
|
||||
IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, std::move(config), parameters.data(), 1);
|
||||
ASSERT_STREQ(app->QueryExeLocation(), L"hello");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@
|
|||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
<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="System.Diagnostics.EventLog" Version="$(SystemDiagnosticsEventLogPackageVersion)" />
|
||||
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using IISIntegration.FunctionalTests.Utilities;
|
||||
|
|
@ -32,20 +33,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
deploymentParameters => deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task InvalidProcessPath_ExpectServerError()
|
||||
[ConditionalTheory]
|
||||
[InlineData("bogus")]
|
||||
[InlineData("c:\\random files\\dotnet.exe")]
|
||||
[InlineData(".\\dotnet.exe")]
|
||||
public async Task InvalidProcessPath_ExpectServerError(string path)
|
||||
{
|
||||
var dotnetLocation = "bogus";
|
||||
|
||||
var deploymentParameters = GetBaseDeploymentParameters();
|
||||
// Point to dotnet installed in user profile.
|
||||
deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet.
|
||||
|
||||
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");
|
||||
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
|
|
@ -77,6 +76,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
await AssertStarts(
|
||||
deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path),
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,8 +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.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IISIntegration.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -11,7 +9,7 @@ using Microsoft.Extensions.Logging.Testing;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Inprocess
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
[SkipIfHostableWebCoreNotAvailible]
|
||||
public class TestServerTest: LoggedTest
|
||||
|
|
|
|||
|
|
@ -21,10 +21,18 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
|||
{
|
||||
if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable))
|
||||
{
|
||||
// enable debug output
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Utilities
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Utilities
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
public class TestServer: IDisposable, IStartup
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue