From 2e540341dbd37e606942ccacb9330ccb2843f79c Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Wed, 20 Sep 2017 16:09:56 -0700 Subject: [PATCH] Adds in-process mode to ANCM. (#152) --- build/dependencies.props | 2 +- src/AspNetCore/AspNetCore.vcxproj | 15 +- src/AspNetCore/Inc/application.h | 77 ++- src/AspNetCore/Inc/applicationmanager.h | 2 +- src/AspNetCore/Inc/aspnetcoreapplication.h | 127 ++++ src/AspNetCore/Inc/aspnetcoreconfig.h | 29 +- src/AspNetCore/Inc/fx_ver.h | 49 ++ src/AspNetCore/Src/aspnetcoreapplication.cxx | 601 +++++++++++++++++++ src/AspNetCore/Src/aspnetcoreconfig.cxx | 50 +- src/AspNetCore/Src/dllmain.cpp | 4 +- src/AspNetCore/Src/fx_ver.cxx | 195 ++++++ src/AspNetCore/Src/precomp.hxx | 5 + src/AspNetCore/Src/proxymodule.cxx | 81 ++- src/AspNetCore/Src/serverprocess.cxx | 4 +- src/AspNetCore/aspnetcore_schema.xml | 1 + src/IISLib/IISLib.vcxproj | 11 +- 16 files changed, 1202 insertions(+), 51 deletions(-) create mode 100644 src/AspNetCore/Inc/aspnetcoreapplication.h create mode 100644 src/AspNetCore/Inc/fx_ver.h create mode 100644 src/AspNetCore/Src/aspnetcoreapplication.cxx create mode 100644 src/AspNetCore/Src/fx_ver.cxx diff --git a/build/dependencies.props b/build/dependencies.props index 72812e4125..64524cd6c4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,4 +5,4 @@ 15.3.0-* 2.3.0-beta2-* - + \ No newline at end of file diff --git a/src/AspNetCore/AspNetCore.vcxproj b/src/AspNetCore/AspNetCore.vcxproj index 192d0f3fdb..b642d81af6 100644 --- a/src/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCore/AspNetCore.vcxproj @@ -1,5 +1,5 @@  - + @@ -26,32 +26,33 @@ AspNetCore aspnetcore false + 10.0.15063.0 DynamicLibrary true - v140 + v141 Unicode DynamicLibrary true - v140 + v141 Unicode false DynamicLibrary false - v140 + v141 true Unicode DynamicLibrary false - v140 + v141 true Unicode @@ -153,6 +154,7 @@ + @@ -171,9 +173,11 @@ + + @@ -181,6 +185,7 @@ + diff --git a/src/AspNetCore/Inc/application.h b/src/AspNetCore/Inc/application.h index ba393d66e7..cecd121b80 100644 --- a/src/AspNetCore/Inc/application.h +++ b/src/AspNetCore/Inc/application.h @@ -147,8 +147,10 @@ class APPLICATION public: APPLICATION() : m_pProcessManager(NULL), m_pApplicationManager(NULL), m_cRefs(1), - m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL) + m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL), + m_pAspNetCoreApplication(NULL) { + InitializeSRWLock(&m_srwLock); } APPLICATION_KEY * @@ -181,6 +183,61 @@ public: return m_pProcessManager->GetProcess( context, pConfig, ppServerProcess ); } + HRESULT + GetAspNetCoreApplication( + _In_ ASPNETCORE_CONFIG *pConfig, + _In_ IHttpContext *context, + _Out_ ASPNETCORE_APPLICATION **ppAspNetCoreApplication + ) + { + HRESULT hr = S_OK; + BOOL fLockTaken = FALSE; + ASPNETCORE_APPLICATION *application; + IHttpApplication *pHttpApplication = context->GetApplication(); + + if (m_pAspNetCoreApplication == NULL) + { + AcquireSRWLockExclusive(&m_srwLock); + fLockTaken = TRUE; + + if (m_pAspNetCoreApplication == NULL) + { + application = new ASPNETCORE_APPLICATION(); + if (application == NULL) { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = application->Initialize(pConfig); + if (FAILED(hr)) + { + goto Finished; + } + + // Assign after initialization + m_pAspNetCoreApplication = application; + } + } + else if (pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId) == NULL) + { + // This means that we are trying to load a second application + // TODO set a flag saying that the whole app pool is invalid ' + // (including the running application) and return 500 every request. + hr = E_FAIL; + goto Finished; + } + + *ppAspNetCoreApplication = m_pAspNetCoreApplication; + + Finished: + if (fLockTaken) + { + ReleaseSRWLockExclusive(&m_srwLock); + } + + return hr; + } + HRESULT Recycle() { @@ -226,14 +283,16 @@ public: private: - STRU m_strAppPhysicalPath; - mutable LONG m_cRefs; - APPLICATION_KEY m_applicationKey; - PROCESS_MANAGER* m_pProcessManager; - APPLICATION_MANAGER *m_pApplicationManager; - BOOL m_fAppOfflineFound; - APP_OFFLINE_HTM *m_pAppOfflineHtm; - FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + STRU m_strAppPhysicalPath; + mutable LONG m_cRefs; + APPLICATION_KEY m_applicationKey; + PROCESS_MANAGER* m_pProcessManager; + APPLICATION_MANAGER *m_pApplicationManager; + BOOL m_fAppOfflineFound; + APP_OFFLINE_HTM *m_pAppOfflineHtm; + FILE_WATCHER_ENTRY *m_pFileWatcherEntry; + ASPNETCORE_APPLICATION *m_pAspNetCoreApplication; + SRWLOCK m_srwLock; }; class APPLICATION_HASH : diff --git a/src/AspNetCore/Inc/applicationmanager.h b/src/AspNetCore/Inc/applicationmanager.h index f254c8db5f..1389d651e9 100644 --- a/src/AspNetCore/Inc/applicationmanager.h +++ b/src/AspNetCore/Inc/applicationmanager.h @@ -9,7 +9,7 @@ class APPLICATION_MANAGER { public: - static + static APPLICATION_MANAGER* GetInstance( VOID diff --git a/src/AspNetCore/Inc/aspnetcoreapplication.h b/src/AspNetCore/Inc/aspnetcoreapplication.h new file mode 100644 index 0000000000..725535ccc8 --- /dev/null +++ b/src/AspNetCore/Inc/aspnetcoreapplication.h @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +typedef void(*request_handler_cb) (int error, IHttpContext* pHttpContext, void* pvCompletionContext); +typedef REQUEST_NOTIFICATION_STATUS(*PFN_REQUEST_HANDLER) (IHttpContext* pHttpContext, void* pvRequstHandlerContext); +typedef BOOL(*PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext); + +class ASPNETCORE_APPLICATION +{ +public: + + ASPNETCORE_APPLICATION(): + m_pConfiguration(NULL), + m_RequestHandler(NULL) + { + } + + ~ASPNETCORE_APPLICATION() + { + if (m_hThread != NULL) + { + CloseHandle(m_hThread); + m_hThread = NULL; + } + + if (m_pInitalizeEvent != NULL) + { + CloseHandle(m_pInitalizeEvent); + m_pInitalizeEvent = NULL; + } + } + + HRESULT + Initialize( + _In_ ASPNETCORE_CONFIG* pConfig + ); + + REQUEST_NOTIFICATION_STATUS + ExecuteRequest( + _In_ IHttpContext* pHttpContext + ); + + VOID + Shutdown( + VOID + ); + + VOID + SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_callback, + _In_ PFN_SHUTDOWN_HANDLER shutdown_callback, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext + ); + + // Executes the .NET Core process + HRESULT + ExecuteApplication( + VOID + ); + + ASPNETCORE_CONFIG* + GetConfig( + VOID + ) + { + return m_pConfiguration; + } + + static + ASPNETCORE_APPLICATION* + GetInstance( + VOID + ) + { + return s_Application; + } + +private: + // Thread executing the .NET Core process + HANDLE m_hThread; + + // Configuration for this application + ASPNETCORE_CONFIG* m_pConfiguration; + + // The request handler callback from managed code + PFN_REQUEST_HANDLER m_RequestHandler; + VOID* m_RequstHandlerContext; + + // The shutdown handler callback from managed code + PFN_SHUTDOWN_HANDLER m_ShutdownHandler; + VOID* m_ShutdownHandlerContext; + + // The event that gets triggered when managed initialization is complete + HANDLE m_pInitalizeEvent; + + // The exit code of the .NET Core process + INT m_ProcessExitCode; + + static ASPNETCORE_APPLICATION* s_Application; + + static VOID + FindDotNetFolders( + _In_ STRU *pstrPath, + _Out_ std::vector *pvFolders + ); + + static HRESULT + FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult + ); + + static BOOL + DirectoryExists( + _In_ STRU *pstrPath + ); + + static BOOL + GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult + ); +}; + diff --git a/src/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCore/Inc/aspnetcoreconfig.h index e83e4301ff..b2a62695dc 100644 --- a/src/AspNetCore/Inc/aspnetcoreconfig.h +++ b/src/AspNetCore/Inc/aspnetcoreconfig.h @@ -27,6 +27,7 @@ #define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE L"recycleOnFileChange" #define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE L"file" #define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH L"path" +#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" #define MAX_RAPID_FAILS_PER_MINUTE 100 #define MILLISECONDS_IN_ONE_SECOND 1000 @@ -57,7 +58,7 @@ public: _In_ IHttpContext *pHttpContext, _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig ); - + ENVIRONMENT_VAR_HASH* QueryEnvironmentVariables( VOID @@ -117,11 +118,19 @@ public: STRU* QueryApplicationPath( VOID - ) + ) { return &m_struApplication; } + STRU* + QueryApplicationFullPath( + VOID + ) + { + return &m_struApplicationFullPath; + } + STRU* QueryProcessPath( VOID @@ -166,6 +175,19 @@ public: return m_fDisableStartUpErrorPage; } + BOOL + QueryIsInProcess() + { + return m_fIsInProcess; + } + + BOOL + QueryIsOutOfProcess() + { + return m_fIsOutOfProcess; + } + + STRU* QueryStdoutLogFile() { @@ -197,11 +219,14 @@ private: STRU m_struArguments; STRU m_struProcessPath; STRU m_struStdoutLogFile; + STRU m_struApplicationFullPath; BOOL m_fStdoutLogEnabled; BOOL m_fForwardWindowsAuthToken; BOOL m_fDisableStartUpErrorPage; BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; + BOOL m_fIsInProcess; + BOOL m_fIsOutOfProcess; ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; }; diff --git a/src/AspNetCore/Inc/fx_ver.h b/src/AspNetCore/Inc/fx_ver.h new file mode 100644 index 0000000000..f485ba5a6e --- /dev/null +++ b/src/AspNetCore/Inc/fx_ver.h @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef __FX_VER_H__ +#define __FX_VER_H__ +#include + +// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not +// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 +struct fx_ver_t +{ + fx_ver_t(int major, int minor, int patch); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); + + int get_major() const { return m_major; } + int get_minor() const { return m_minor; } + int get_patch() const { return m_patch; } + + void set_major(int m) { m_major = m; } + void set_minor(int m) { m_minor = m; } + void set_patch(int p) { m_patch = p; } + + bool is_prerelease() const { return !m_pre.empty(); } + + std::wstring as_str() const; + std::wstring prerelease_glob() const; + std::wstring patch_glob() const; + + bool operator ==(const fx_ver_t& b) const; + bool operator !=(const fx_ver_t& b) const; + bool operator <(const fx_ver_t& b) const; + bool operator >(const fx_ver_t& b) const; + bool operator <=(const fx_ver_t& b) const; + bool operator >=(const fx_ver_t& b) const; + + static bool parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production = false); + +private: + int m_major; + int m_minor; + int m_patch; + std::wstring m_pre; + std::wstring m_build; + + static int compare(const fx_ver_t&a, const fx_ver_t& b); +}; + +#endif // __FX_VER_H__ \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreapplication.cxx b/src/AspNetCore/Src/aspnetcoreapplication.cxx new file mode 100644 index 0000000000..3e2f727f5a --- /dev/null +++ b/src/AspNetCore/Src/aspnetcoreapplication.cxx @@ -0,0 +1,601 @@ +#include "precomp.hxx" +#include "fx_ver.h" +#include + +typedef DWORD(*hostfxr_main_fn) (CONST DWORD argc, CONST WCHAR* argv[]); + +// Initialization export + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +register_callbacks( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + ASPNETCORE_APPLICATION::GetInstance()->SetCallbackHandles( + request_handler, + shutdown_handler, + pvRequstHandlerContext, + pvShutdownHandlerContext + ); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_REQUEST* +http_get_raw_request( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetRequest()->GetRawHttpRequest(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HTTP_RESPONSE* +http_get_raw_response( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->GetResponse()->GetRawHttpResponse(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT VOID http_set_response_status_code( + _In_ IHttpContext* pHttpContext, + _In_ USHORT statusCode, + _In_ PCSTR pszReason +) +{ + pHttpContext->GetResponse()->SetStatus(statusCode, pszReason); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_post_completion( + _In_ IHttpContext* pHttpContext +) +{ + return pHttpContext->PostCompletion(0); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_indicate_completion( + _In_ IHttpContext* pHttpContext, + _In_ REQUEST_NOTIFICATION_STATUS notificationStatus +) +{ + pHttpContext->IndicateCompletion(notificationStatus); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_get_completion_info( + _In_ IHttpCompletionInfo2* info, + _Out_ DWORD* cbBytes, + _Out_ HRESULT* hr +) +{ + *cbBytes = info->GetCompletionBytes(); + *hr = info->GetCompletionStatus(); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +BSTR // TODO probably should make this a wide string +http_get_application_full_path() +{ + return SysAllocString(ASPNETCORE_APPLICATION::GetInstance()->GetConfig()->QueryApplicationFullPath()->QueryStr()); +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_read_request_bytes( + _In_ IHttpContext* pHttpContext, + _In_ CHAR* pvBuffer, + _In_ DWORD cbBuffer, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ DWORD* pDwBytesReceived, + _In_ BOOL* pfCompletionPending +) +{ + IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pHttpContext->GetRequest(); + + BOOL fAsync = TRUE; + + HRESULT hr = pHttpRequest->ReadEntityBody( + pvBuffer, + cbBuffer, + fAsync, + pfnCompletionCallback, + pvCompletionContext, + pDwBytesReceived, + pfCompletionPending); + + if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + // We reached the end of the data + hr = S_OK; + } + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_write_response_bytes( + _In_ IHttpContext* pHttpContext, + _In_ HTTP_DATA_CHUNK* pDataChunks, + _In_ DWORD nChunks, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->WriteEntityChunks( + pDataChunks, + nChunks, + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + + return hr; +} + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +HRESULT +http_flush_response_bytes( + _In_ IHttpContext* pHttpContext, + _In_ PFN_ASYNC_COMPLETION pfnCompletionCallback, + _In_ VOID* pvCompletionContext, + _In_ BOOL* pfCompletionExpected +) +{ + IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pHttpContext->GetResponse(); + + BOOL fAsync = TRUE; + BOOL fMoreData = TRUE; + DWORD dwBytesSent; + + HRESULT hr = pHttpResponse->Flush( + fAsync, + fMoreData, + pfnCompletionCallback, + pvCompletionContext, + &dwBytesSent, + pfCompletionExpected); + return hr; +} + +// Thread execution callback +static +VOID +ExecuteAspNetCoreProcess( + _In_ LPVOID pContext +) +{ + HRESULT hr; + ASPNETCORE_APPLICATION *pApplication = (ASPNETCORE_APPLICATION*)pContext; + + hr = pApplication->ExecuteApplication(); + if (hr != S_OK) + { + // TODO log error + } +} + +ASPNETCORE_APPLICATION* +ASPNETCORE_APPLICATION::s_Application = NULL; + +VOID +ASPNETCORE_APPLICATION::SetCallbackHandles( + _In_ PFN_REQUEST_HANDLER request_handler, + _In_ PFN_SHUTDOWN_HANDLER shutdown_handler, + _In_ VOID* pvRequstHandlerContext, + _In_ VOID* pvShutdownHandlerContext +) +{ + m_RequestHandler = request_handler; + m_RequstHandlerContext = pvRequstHandlerContext; + m_ShutdownHandler = shutdown_handler; + m_ShutdownHandlerContext = pvShutdownHandlerContext; + + // Initialization complete + SetEvent(m_pInitalizeEvent); +} + +HRESULT +ASPNETCORE_APPLICATION::Initialize( + _In_ ASPNETCORE_CONFIG * pConfig +) +{ + HRESULT hr = S_OK; + + DWORD dwTimeout; + DWORD dwResult; + DBG_ASSERT(pConfig != NULL); + + m_pConfiguration = pConfig; + + m_pInitalizeEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not set + NULL); // name + + if (m_pInitalizeEvent == NULL) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + m_hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (m_hThread == NULL) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + // If the debugger is attached, never timeout + if (IsDebuggerPresent()) + { + dwTimeout = INFINITE; + } + else + { + dwTimeout = pConfig->QueryStartupTimeLimitInMS(); + } + + const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent }; + + // Wait on either the thread to complete or the event to be set + dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout); + + // It all timed out + if (dwResult == WAIT_TIMEOUT) + { + return HRESULT_FROM_WIN32(dwResult); + } + else if (dwResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + dwResult = WaitForSingleObject(m_hThread, 0); + + // The thread ended it means that something failed + if (dwResult == WAIT_OBJECT_0) + { + return HRESULT_FROM_WIN32(dwResult); + } + else if (dwResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +HRESULT +ASPNETCORE_APPLICATION::ExecuteApplication( + VOID +) +{ + HRESULT hr = S_OK; + + STRU strFullPath; + STRU strDotnetExeLocation; + STRU strHostFxrSearchExpression; + STRU strDotnetFolderLocation; + STRU strHighestDotnetVersion; + STRU strApplicationFullPath; + PWSTR strDelimeterContext = NULL; + PCWSTR pszDotnetExeLocation = NULL; + PCWSTR pszDotnetExeString(L"dotnet.exe"); + DWORD dwCopyLength; + HMODULE hModule; + PCWSTR argv[2]; + hostfxr_main_fn pProc; + std::vector vVersionFolders; + + // Get the System PATH value. + if (!GetEnv(L"PATH", &strFullPath)) + { + goto Failed; + } + + // Split on ';', checking to see if dotnet.exe exists in any folders. + pszDotnetExeLocation = wcstok_s(strFullPath.QueryStr(), L";", &strDelimeterContext); + + while (pszDotnetExeLocation != NULL) + { + dwCopyLength = wcsnlen_s(pszDotnetExeLocation, 260); + if (dwCopyLength == 0) + { + continue; + } + + // We store both the exe and folder locations as we eventually need to check inside of host\\fxr + // which doesn't need the dotnet.exe portion of the string + // TODO consider reducing allocations. + strDotnetExeLocation.Reset(); + strDotnetFolderLocation.Reset(); + hr = strDotnetExeLocation.Copy(pszDotnetExeLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Failed; + } + + hr = strDotnetFolderLocation.Copy(pszDotnetExeLocation, dwCopyLength); + if (FAILED(hr)) + { + goto Failed; + } + + if (dwCopyLength > 0 && pszDotnetExeLocation[dwCopyLength - 1] != L'\\') + { + hr = strDotnetExeLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Failed; + } + } + + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Failed; + } + + if (PathFileExists(strDotnetExeLocation.QueryStr())) + { + // means we found the folder with a dotnet.exe inside of it. + break; + } + pszDotnetExeLocation = wcstok_s(NULL, L";", &strDelimeterContext); + } + + hr = strDotnetFolderLocation.Append(L"\\host\\fxr"); + if (FAILED(hr)) + { + goto Failed; + } + + if (!DirectoryExists(&strDotnetFolderLocation)) + { + goto Failed; + } + + // Find all folders under host\\fxr\\ for version numbers. + hr = strHostFxrSearchExpression.Copy(strDotnetFolderLocation); + if (FAILED(hr)) + { + goto Failed; + } + + hr = strHostFxrSearchExpression.Append(L"\\*"); + if (FAILED(hr)) + { + goto Failed; + } + + // As we use the logic from core-setup, we are opting to use std here. + // TODO remove all uses of std? + FindDotNetFolders(&strHostFxrSearchExpression, &vVersionFolders); + + if (vVersionFolders.size() == 0) + { + goto Failed; + } + + hr = FindHighestDotNetVersion(vVersionFolders, &strHighestDotnetVersion); + if (FAILED(hr)) + { + goto Failed; + } + hr = strDotnetFolderLocation.Append(L"\\"); + if (FAILED(hr)) + { + goto Failed; + } + + hr = strDotnetFolderLocation.Append(strHighestDotnetVersion.QueryStr()); + if (FAILED(hr)) + { + goto Failed; + + } + + hr = strDotnetFolderLocation.Append(L"\\hostfxr.dll"); + if (FAILED(hr)) + { + goto Failed; + } + + hModule = LoadLibraryW(strDotnetFolderLocation.QueryStr()); + + if (hModule == NULL) + { + // .NET Core not installed (we can log a more detailed error message here) + goto Failed; + } + + // Get the entry point for main + pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main"); + if (pProc == NULL) { + goto Failed; + } + + // The first argument is mostly ignored + hr = strDotnetExeLocation.Append(pszDotnetExeString); + if (FAILED(hr)) + { + goto Failed; + } + + argv[0] = strDotnetExeLocation.QueryStr(); + PATH::ConvertPathToFullPath(m_pConfiguration->QueryArguments()->QueryStr(), m_pConfiguration->QueryApplicationFullPath()->QueryStr(), &strApplicationFullPath); + argv[1] = strApplicationFullPath.QueryStr(); + + // There can only ever be a single instance of .NET Core + // loaded in the process but we need to get config information to boot it up in the + // first place. This is happening in an execute request handler and everyone waits + // until this initialization is done. + + // We set a static so that managed code can call back into this instance and + // set the callbacks + s_Application = this; + + m_ProcessExitCode = pProc(2, argv); + if (m_ProcessExitCode != 0) + { + // TODO error + } + + return hr; +Failed: + // TODO log any errors + return hr; +} + +BOOL +ASPNETCORE_APPLICATION::GetEnv( + _In_ PCWSTR pszEnvironmentVariable, + _Out_ STRU *pstrResult +) +{ + DWORD dwLength; + PWSTR pszBuffer= NULL; + BOOL fSucceeded = FALSE; + + if (pszEnvironmentVariable == NULL) + { + goto Finished; + } + pstrResult->Reset(); + dwLength = GetEnvironmentVariableW(pszEnvironmentVariable, NULL, 0); + + if (dwLength == 0) + { + goto Finished; + } + + pszBuffer = new WCHAR[dwLength]; + if (GetEnvironmentVariableW(pszEnvironmentVariable, pszBuffer, dwLength) == 0) + { + goto Finished; + } + + pstrResult->Copy(pszBuffer); + + fSucceeded = TRUE; + +Finished: + if (pszBuffer != NULL) { + delete[] pszBuffer; + } + return fSucceeded; +} + +VOID +ASPNETCORE_APPLICATION::FindDotNetFolders( + _In_ STRU *pstrPath, + _Out_ std::vector *pvFolders +) +{ + HANDLE handle = NULL; + WIN32_FIND_DATAW data = { 0 }; + + handle = FindFirstFileExW(pstrPath->QueryStr(), FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); + if (handle == INVALID_HANDLE_VALUE) + { + return; + } + + do + { + std::wstring folder(data.cFileName); + pvFolders->push_back(folder); + } while (FindNextFileW(handle, &data)); + + FindClose(handle); +} + +HRESULT +ASPNETCORE_APPLICATION::FindHighestDotNetVersion( + _In_ std::vector vFolders, + _Out_ STRU *pstrResult +) +{ + HRESULT hr = S_OK; + fx_ver_t max_ver(-1, -1, -1); + for (const auto& dir : vFolders) + { + fx_ver_t fx_ver(-1, -1, -1); + if (fx_ver_t::parse(dir, &fx_ver, false)) + { + max_ver = std::max(max_ver, fx_ver); + } + } + + hr = pstrResult->Copy(max_ver.as_str().c_str()); + + // we check FAILED(hr) outside of function + return hr; +} + +BOOL +ASPNETCORE_APPLICATION::DirectoryExists( + _In_ STRU *pstrPath +) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + + if (pstrPath->IsEmpty()) + { + return false; + } + + return GetFileAttributesExW(pstrPath->QueryStr(), GetFileExInfoStandard, &data); +} + +REQUEST_NOTIFICATION_STATUS +ASPNETCORE_APPLICATION::ExecuteRequest( + _In_ IHttpContext* pHttpContext +) +{ + if (m_RequestHandler != NULL) + { + return m_RequestHandler(pHttpContext, m_RequstHandlerContext); + } + + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); + return RQ_NOTIFICATION_FINISH_REQUEST; +} + + +VOID +ASPNETCORE_APPLICATION::Shutdown( + VOID +) +{ + // First call into the managed server and shutdown + BOOL result = m_ShutdownHandler(m_ShutdownHandlerContext); + s_Application = NULL; + delete this; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/aspnetcoreconfig.cxx b/src/AspNetCore/Src/aspnetcoreconfig.cxx index e919eb7d9b..944ff75e28 100644 --- a/src/AspNetCore/Src/aspnetcoreconfig.cxx +++ b/src/AspNetCore/Src/aspnetcoreconfig.cxx @@ -8,11 +8,16 @@ ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() // // the destructor will be called once IIS decides to recycle the module context (i.e., application) // + // shutting down core application first + if (ASPNETCORE_APPLICATION::GetInstance() != NULL) { + ASPNETCORE_APPLICATION::GetInstance()->Shutdown(); + } + m_struApplicationFullPath.Reset(); if (!m_struApplication.IsEmpty()) { APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); } - if(m_pEnvironmentVariables != NULL) + if (m_pEnvironmentVariables != NULL) { m_pEnvironmentVariables->Clear(); delete m_pEnvironmentVariables; @@ -86,7 +91,7 @@ ASPNETCORE_CONFIG::GetConfig( else { // set appliction info here instead of inside Populate() - // as the destructor will delete the backend process + // as the destructor will delete the backend process hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); if (FAILED(hr)) { @@ -118,6 +123,8 @@ ASPNETCORE_CONFIG::Populate( STRU strEnvName; STRU strEnvValue; STRU strExpandedEnvValue; + STRU strApplicationFullPath; + STRU strHostingModel; IAppHostAdminManager *pAdminManager = NULL; IAppHostElement *pAspNetCoreElement = NULL; IAppHostElement *pWindowsAuthenticationElement = NULL; @@ -144,13 +151,18 @@ ASPNETCORE_CONFIG::Populate( } pAdminManager = g_pHttpServer->GetAdminManager(); - hr = strSiteConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); if (FAILED(hr)) { goto Finished; } + hr = m_struApplicationFullPath.Copy(pHttpContext->GetApplication()->GetApplicationPhysicalPath()); + if (FAILED(hr)) + { + goto Finished; + } + hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, strSiteConfigPath.QueryStr(), &pWindowsAuthenticationElement); @@ -224,6 +236,23 @@ ASPNETCORE_CONFIG::Populate( goto Finished; } + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_HOSTING_MODEL, + &strHostingModel); + if (FAILED(hr)) + { + goto Finished; + } + + if (strHostingModel.IsEmpty() || strHostingModel.Equals(L"outofprocess", TRUE)) + { + m_fIsOutOfProcess = TRUE; + } + else if (strHostingModel.Equals(L"inprocess", TRUE)) + { + m_fIsInProcess = TRUE; + } + hr = GetElementStringProperty(pAspNetCoreElement, CS_ASPNETCORE_PROCESS_ARGUMENTS, &m_struArguments); @@ -314,14 +343,13 @@ ASPNETCORE_CONFIG::Populate( { goto Finished; } - - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_STDOUT_LOG_FILE, - &m_struStdoutLogFile); - if (FAILED(hr)) - { - goto Finished; - } + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_FILE, + &m_struStdoutLogFile); + if (FAILED(hr)) + { + goto Finished; + } hr = GetElementChildByName(pAspNetCoreElement, CS_ASPNETCORE_ENVIRONMENT_VARIABLES, diff --git a/src/AspNetCore/Src/dllmain.cpp b/src/AspNetCore/Src/dllmain.cpp index 2aaf5d7044..e510dc535d 100644 --- a/src/AspNetCore/Src/dllmain.cpp +++ b/src/AspNetCore/Src/dllmain.cpp @@ -210,6 +210,7 @@ HRESULT // static object initialized. // pFactory = new CProxyModuleFactory; + if (pFactory == NULL) { hr = E_OUTOFMEMORY; @@ -225,7 +226,8 @@ HRESULT goto Finished; } - pFactory = NULL; + pFactory = NULL; + g_pResponseHeaderHash = new RESPONSE_HEADER_HASH; if (g_pResponseHeaderHash == NULL) { diff --git a/src/AspNetCore/Src/fx_ver.cxx b/src/AspNetCore/Src/fx_ver.cxx new file mode 100644 index 0000000000..1c844d3113 --- /dev/null +++ b/src/AspNetCore/Src/fx_ver.cxx @@ -0,0 +1,195 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include +#include +#include "fx_ver.h" +#include "precomp.h" + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build) + : m_major(major) + , m_minor(minor) + , m_patch(patch) + , m_pre(pre) + , m_build(build) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const std::wstring& pre) + : fx_ver_t(major, minor, patch, pre, TEXT("")) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch) + : fx_ver_t(major, minor, patch, TEXT(""), TEXT("")) +{ +} + +bool fx_ver_t::operator ==(const fx_ver_t& b) const +{ + return compare(*this, b) == 0; +} + +bool fx_ver_t::operator !=(const fx_ver_t& b) const +{ + return !operator ==(b); +} + +bool fx_ver_t::operator <(const fx_ver_t& b) const +{ + return compare(*this, b) < 0; +} + +bool fx_ver_t::operator >(const fx_ver_t& b) const +{ + return compare(*this, b) > 0; +} + +bool fx_ver_t::operator <=(const fx_ver_t& b) const +{ + return compare(*this, b) <= 0; +} + +bool fx_ver_t::operator >=(const fx_ver_t& b) const +{ + return compare(*this, b) >= 0; +} + +std::wstring fx_ver_t::as_str() const +{ + std::wstringstream stream; + stream << m_major << TEXT(".") << m_minor << TEXT(".") << m_patch; + if (!m_pre.empty()) + { + stream << m_pre; + } + if (!m_build.empty()) + { + stream << TEXT("+") << m_build; + } + return stream.str(); +} + +/* static */ +int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b) +{ + // compare(u.v.w-p+b, x.y.z-q+c) + if (a.m_major != b.m_major) + { + return (a.m_major > b.m_major) ? 1 : -1; + } + + if (a.m_minor != b.m_minor) + { + return (a.m_minor > b.m_minor) ? 1 : -1; + } + + if (a.m_patch != b.m_patch) + { + return (a.m_patch > b.m_patch) ? 1 : -1; + } + + if (a.m_pre.empty() != b.m_pre.empty()) + { + // Either a is empty or b is empty + return a.m_pre.empty() ? 1 : -1; + } + + // Either both are empty or both are non-empty (may be equal) + int pre_cmp = a.m_pre.compare(b.m_pre); + if (pre_cmp != 0) + { + return pre_cmp; + } + + return a.m_build.compare(b.m_build); +} + +bool try_stou(const std::wstring& str, unsigned* num) +{ + if (str.empty()) + { + return false; + } + if (str.find_first_not_of(TEXT("0123456789")) != std::wstring::npos) + { + return false; + } + *num = (unsigned)std::stoul(str); + return true; +} + +bool parse_internal(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + size_t maj_start = 0; + size_t maj_sep = ver.find(TEXT('.')); + if (maj_sep == std::wstring::npos) + { + return false; + } + unsigned major = 0; + if (!try_stou(ver.substr(maj_start, maj_sep), &major)) + { + return false; + } + + size_t min_start = maj_sep + 1; + size_t min_sep = ver.find(TEXT('.'), min_start); + if (min_sep == std::wstring::npos) + { + return false; + } + + unsigned minor = 0; + if (!try_stou(ver.substr(min_start, min_sep - min_start), &minor)) + { + return false; + } + + unsigned patch = 0; + size_t pat_start = min_sep + 1; + size_t pat_sep = ver.find_first_not_of(TEXT("0123456789"), pat_start); + if (pat_sep == std::wstring::npos) + { + if (!try_stou(ver.substr(pat_start), &patch)) + { + return false; + } + + *fx_ver = fx_ver_t(major, minor, patch); + return true; + } + + if (parse_only_production) + { + // This is a prerelease or has build suffix. + return false; + } + + if (!try_stou(ver.substr(pat_start, pat_sep - pat_start), &patch)) + { + return false; + } + + size_t pre_start = pat_sep; + size_t pre_sep = ver.find(TEXT('+'), pre_start); + if (pre_sep == std::wstring::npos) + { + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start)); + return true; + } + else + { + size_t build_start = pre_sep + 1; + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start, pre_sep - pre_start), ver.substr(build_start)); + return true; + } +} + +/* static */ +bool fx_ver_t::parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + bool valid = parse_internal(ver, fx_ver, parse_only_production); + assert(!valid || fx_ver->as_str() == ver); + return valid; +} \ No newline at end of file diff --git a/src/AspNetCore/Src/precomp.hxx b/src/AspNetCore/Src/precomp.hxx index bdd540d23a..178c66698f 100644 --- a/src/AspNetCore/Src/precomp.hxx +++ b/src/AspNetCore/Src/precomp.hxx @@ -42,6 +42,10 @@ #include #include +#include +#include +#include + // // Option available starting Windows 8. // 111 is the value in SDK on May 15, 2012. @@ -110,6 +114,7 @@ inline bool IsSpace(char ch) #include "environmentvariablehash.h" #include "..\aspnetcore_msg.h" #include "aspnetcoreconfig.h" +#include "aspnetcoreapplication.h" #include "serverprocess.h" #include "processmanager.h" #include "filewatcher.h" diff --git a/src/AspNetCore/Src/proxymodule.cxx b/src/AspNetCore/Src/proxymodule.cxx index d19f4488ff..502d91f035 100644 --- a/src/AspNetCore/Src/proxymodule.cxx +++ b/src/AspNetCore/Src/proxymodule.cxx @@ -3,7 +3,6 @@ #include "precomp.hxx" - __override HRESULT CProxyModuleFactory::GetHttpModule( @@ -79,32 +78,86 @@ CProxyModule::OnExecuteRequestHandler( IHttpEventProvider * ) { - m_pHandler = new FORWARDING_HANDLER(pHttpContext); - if (m_pHandler == NULL) - { - pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY); - return RQ_NOTIFICATION_FINISH_REQUEST; - } + HRESULT hr; + APPLICATION_MANAGER* pApplicationManager; + APPLICATION* pApplication; + ASPNETCORE_CONFIG* config; + ASPNETCORE_APPLICATION* pAspNetCoreApplication; + ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - return m_pHandler->OnExecuteRequestHandler(); + // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? + if (config->QueryIsOutOfProcess())// case insensitive + { + m_pHandler = new FORWARDING_HANDLER(pHttpContext); + if (m_pHandler == NULL) + { + hr = E_OUTOFMEMORY; + goto Failed; + } + + return m_pHandler->OnExecuteRequestHandler(); + } + else if (config->QueryIsInProcess()) + { + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failed; + } + + hr = pApplicationManager->GetApplication(pHttpContext, + &pApplication); + if (FAILED(hr)) + { + goto Failed; + } + + hr = pApplication->GetAspNetCoreApplication(config, pHttpContext, &pAspNetCoreApplication); + if (FAILED(hr)) + { + goto Failed; + } + + // Allow reading and writing to simultaneously + ((IHttpContext3*)pHttpContext)->EnableFullDuplex(); + + // Disable response buffering by default, we'll do a write behind buffering in managed code + ((IHttpResponse2*)pHttpContext->GetResponse())->DisableBuffering(); + + // TODO: Optimize sync completions + return pAspNetCoreApplication->ExecuteRequest(pHttpContext); + } +Failed: + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, hr); + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; } __override REQUEST_NOTIFICATION_STATUS CProxyModule::OnAsyncCompletion( - IHttpContext *, + IHttpContext * pHttpContext, DWORD dwNotification, BOOL fPostNotification, IHttpEventProvider *, IHttpCompletionInfo * pCompletionInfo ) { - UNREFERENCED_PARAMETER(dwNotification); - UNREFERENCED_PARAMETER(fPostNotification); - DBG_ASSERT(dwNotification == RQ_EXECUTE_REQUEST_HANDLER); - DBG_ASSERT(fPostNotification == FALSE); + // TODO store whether we are inproc or outofproc so we don't need to check the config everytime? + ASPNETCORE_CONFIG* config; + ASPNETCORE_CONFIG::GetConfig(pHttpContext, &config); - return m_pHandler->OnAsyncCompletion( + if (config->QueryIsOutOfProcess()) + { + return m_pHandler->OnAsyncCompletion( pCompletionInfo->GetCompletionBytes(), pCompletionInfo->GetCompletionStatus()); + } + else if (config->QueryIsInProcess()) + { + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_CONTINUE; + } + + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_APPLICATION_ACTIVATION_EXEC_FAILURE); + return REQUEST_NOTIFICATION_STATUS::RQ_NOTIFICATION_FINISH_REQUEST; } \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 9487d2a5ca..e8922bd1f0 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -832,7 +832,7 @@ SERVER_PROCESS::PostStartCheck( pStruErrorMessage->SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath, + m_pszRootApplicationPath.QueryStr(), pStruCommandline->QueryStr(), m_dwPort, hr); @@ -1071,7 +1071,7 @@ Finished: strEventMsg.SafeSnwprintf( ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, m_struAppFullPath.QueryStr(), - m_pszRootApplicationPath, + m_pszRootApplicationPath.QueryStr(), struCommandLine.QueryStr(), hr); } diff --git a/src/AspNetCore/aspnetcore_schema.xml b/src/AspNetCore/aspnetcore_schema.xml index ca41d48691..c00cc8a77c 100644 --- a/src/AspNetCore/aspnetcore_schema.xml +++ b/src/AspNetCore/aspnetcore_schema.xml @@ -27,6 +27,7 @@ + diff --git a/src/IISLib/IISLib.vcxproj b/src/IISLib/IISLib.vcxproj index 8ff54a4c75..296e723b3f 100644 --- a/src/IISLib/IISLib.vcxproj +++ b/src/IISLib/IISLib.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -23,31 +23,32 @@ Win32Proj IISLib IISLib + 10.0.15063.0 StaticLibrary true - v140 + v141 Unicode StaticLibrary true - v140 + v141 Unicode StaticLibrary false - v140 + v141 true Unicode StaticLibrary false - v140 + v141 true Unicode