Display startup errors in ANCM (#8518)
This commit is contained in:
parent
eb41de88a9
commit
56c064bd53
|
|
@ -186,6 +186,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
|||
{
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.Headers[HeaderNames.CacheControl] = "no-cache";
|
||||
context.Response.ContentType = "text/html; charset=utf-8";
|
||||
return errorPage.ExecuteAsync(context);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)RazorViews\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)StackTrace\**\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)ErrorPage\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -117,15 +117,6 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ErrorPageHtml_Title" xml:space="preserve">
|
||||
<value>Internal Server Error</value>
|
||||
</data>
|
||||
<data name="ErrorPageHtml_UnhandledException" xml:space="preserve">
|
||||
<value>An error occurred while starting the application.</value>
|
||||
</data>
|
||||
<data name="ErrorPageHtml_UnknownLocation" xml:space="preserve">
|
||||
<value>Unknown location</value>
|
||||
</data>
|
||||
<data name="WebHostBuilder_SingleInstance" xml:space="preserve">
|
||||
<value>WebHostBuilder allows creation only of a single instance of WebHost</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#define CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY L"setCurrentDirectory"
|
||||
#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage"
|
||||
#define CS_ENABLED L"enabled"
|
||||
#define CS_ASPNETCORE_HANDLER_CALL_STARTUP_HOOK L"callStartupHook"
|
||||
|
||||
class ConfigurationSection: NonCopyable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,31 +9,34 @@
|
|||
class ServerErrorHandler : public REQUEST_HANDLER
|
||||
{
|
||||
public:
|
||||
ServerErrorHandler(IHttpContext& pContext, USHORT statusCode, USHORT subStatusCode, const std::string& statusText, HRESULT hr, HINSTANCE module, bool disableStartupPage, int page) noexcept
|
||||
: ServerErrorHandler(pContext, statusCode, subStatusCode, statusText, hr, module, disableStartupPage, page, std::vector<byte>())
|
||||
{
|
||||
}
|
||||
|
||||
ServerErrorHandler(IHttpContext &pContext, USHORT statusCode, USHORT subStatusCode, std::string statusText, HRESULT hr, HINSTANCE moduleInstance, bool disableStartupPage, int page) noexcept
|
||||
ServerErrorHandler(IHttpContext& pContext, USHORT statusCode, USHORT subStatusCode, const std::string& statusText, HRESULT hr, HINSTANCE module, bool disableStartupPage, int page, const std::vector<byte>& content) noexcept
|
||||
: REQUEST_HANDLER(pContext),
|
||||
m_pContext(pContext),
|
||||
m_HR(hr),
|
||||
m_disableStartupPage(disableStartupPage),
|
||||
m_page(page),
|
||||
m_moduleInstance(moduleInstance),
|
||||
m_statusCode(statusCode),
|
||||
m_subStatusCode(subStatusCode),
|
||||
m_statusText(std::move(statusText))
|
||||
m_pContext(pContext),
|
||||
m_HR(hr),
|
||||
m_disableStartupPage(disableStartupPage),
|
||||
m_statusCode(statusCode),
|
||||
m_subStatusCode(subStatusCode),
|
||||
m_statusText(std::move(statusText)),
|
||||
m_page(page),
|
||||
m_ExceptionInfoContent(content),
|
||||
m_moduleInstance(module)
|
||||
{
|
||||
}
|
||||
|
||||
REQUEST_NOTIFICATION_STATUS ExecuteRequestHandler() override
|
||||
{
|
||||
static std::string s_html500Page = GetHtml(m_moduleInstance, m_page);
|
||||
|
||||
WriteStaticResponse(m_pContext, s_html500Page, m_HR, m_disableStartupPage);
|
||||
WriteStaticResponse(m_pContext, m_HR, m_disableStartupPage);
|
||||
|
||||
return RQ_NOTIFICATION_FINISH_REQUEST;
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteStaticResponse(IHttpContext& pContext, std::string &page, HRESULT hr, bool disableStartupErrorPage) const
|
||||
void WriteStaticResponse(IHttpContext& pContext, HRESULT hr, bool disableStartupErrorPage)
|
||||
{
|
||||
if (disableStartupErrorPage)
|
||||
{
|
||||
|
|
@ -49,14 +52,23 @@ private:
|
|||
(USHORT)strlen("text/html"),
|
||||
FALSE
|
||||
);
|
||||
dataChunk.DataChunkType = HttpDataChunkFromMemory;
|
||||
|
||||
dataChunk.FromMemory.pBuffer = page.data();
|
||||
dataChunk.FromMemory.BufferLength = static_cast<ULONG>(page.size());
|
||||
dataChunk.DataChunkType = HttpDataChunkFromMemory;
|
||||
if (m_ExceptionInfoContent.size() > 0)
|
||||
{
|
||||
dataChunk.FromMemory.pBuffer = &m_ExceptionInfoContent[0];
|
||||
dataChunk.FromMemory.BufferLength = static_cast<ULONG>(m_ExceptionInfoContent.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
static std::string s_html500Page = GetHtml(m_moduleInstance, m_page);
|
||||
dataChunk.FromMemory.pBuffer = s_html500Page.data();
|
||||
dataChunk.FromMemory.BufferLength = static_cast<ULONG>(s_html500Page.size());
|
||||
}
|
||||
|
||||
pResponse->WriteEntityChunkByReference(&dataChunk);
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
GetHtml(HMODULE module, int page)
|
||||
{
|
||||
|
|
@ -91,7 +103,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
IHttpContext &m_pContext;
|
||||
IHttpContext& m_pContext;
|
||||
HRESULT m_HR;
|
||||
bool m_disableStartupPage;
|
||||
int m_page;
|
||||
|
|
@ -99,4 +111,5 @@ private:
|
|||
USHORT m_statusCode;
|
||||
USHORT m_subStatusCode;
|
||||
std::string m_statusText;
|
||||
std::vector<byte> m_ExceptionInfoContent;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using ::testing::NiceMock;
|
|||
// Externals defined in inprocess
|
||||
BOOL g_fProcessDetach;
|
||||
HANDLE g_hEventLog;
|
||||
std::wstring g_exceptionEventLog;
|
||||
|
||||
namespace InprocessTests
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -57,6 +57,7 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc
|
|||
|
||||
const auto handlerSettings = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);
|
||||
m_fSetCurrentDirectory = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_SET_CURRENT_DIRECTORY).value_or(L"true"), L"true");
|
||||
m_fCallStartupHook = equals_ignore_case(find_element(handlerSettings, CS_ASPNETCORE_HANDLER_CALL_STARTUP_HOOK).value_or(L"true"), L"true");
|
||||
|
||||
m_dwStartupTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT) * 1000;
|
||||
m_dwShutdownTimeLimitInMS = aspNetCoreSection->GetRequiredLong(CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT) * 1000;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ public:
|
|||
return m_fSetCurrentDirectory;
|
||||
}
|
||||
|
||||
bool
|
||||
QueryCallStartupHook() const
|
||||
{
|
||||
return m_fCallStartupHook;
|
||||
}
|
||||
|
||||
bool
|
||||
QueryWindowsAuthEnabled() const
|
||||
{
|
||||
|
|
@ -116,6 +122,7 @@ private:
|
|||
bool m_fStdoutLogEnabled;
|
||||
bool m_fDisableStartUpErrorPage;
|
||||
bool m_fSetCurrentDirectory;
|
||||
bool m_fCallStartupHook;
|
||||
bool m_fWindowsAuthEnabled;
|
||||
bool m_fBasicAuthEnabled;
|
||||
bool m_fAnonymousAuthEnabled;
|
||||
|
|
|
|||
|
|
@ -15,30 +15,28 @@ public:
|
|||
IHttpApplication& pApplication,
|
||||
HINSTANCE moduleInstance,
|
||||
BOOL disableLogs,
|
||||
HRESULT hr)
|
||||
HRESULT hr,
|
||||
std::vector<byte>&& errorPageContent
|
||||
)
|
||||
: m_disableLogs(disableLogs),
|
||||
m_HR(hr),
|
||||
m_moduleInstance(moduleInstance),
|
||||
m_errorPageContent(std::move(errorPageContent)),
|
||||
InProcessApplicationBase(pServer, pApplication)
|
||||
{
|
||||
}
|
||||
|
||||
~StartupExceptionApplication() = default;
|
||||
|
||||
HRESULT CreateHandler(IHttpContext *pHttpContext, IREQUEST_HANDLER ** pRequestHandler)
|
||||
HRESULT CreateHandler(IHttpContext* pHttpContext, IREQUEST_HANDLER** pRequestHandler)
|
||||
{
|
||||
*pRequestHandler = new ServerErrorHandler(*pHttpContext, 500, 30, "Internal Server Error", m_HR, m_moduleInstance, m_disableLogs, IN_PROCESS_RH_STATIC_HTML);
|
||||
*pRequestHandler = new ServerErrorHandler(*pHttpContext, 500, 30, "Internal Server Error", m_HR, m_moduleInstance, m_disableLogs, IN_PROCESS_RH_STATIC_HTML, m_errorPageContent);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::string&
|
||||
GetStaticHtml500Content()
|
||||
{
|
||||
return html500Page;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string html500Page;
|
||||
std::vector<byte> m_errorPageContent;
|
||||
BOOL m_disableLogs;
|
||||
HRESULT m_HR;
|
||||
HINSTANCE m_moduleInstance;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ HINSTANCE g_hWinHttpModule;
|
|||
HINSTANCE g_hAspNetCoreModule;
|
||||
HANDLE g_hEventLog = NULL;
|
||||
bool g_fInProcessApplicationCreated = false;
|
||||
std::vector<byte> g_errorPageContent;
|
||||
HINSTANCE g_hServerModule;
|
||||
|
||||
HRESULT
|
||||
|
|
@ -128,7 +129,7 @@ CreateApplication(
|
|||
std::unique_ptr<InProcessOptions> options;
|
||||
THROW_IF_FAILED(InProcessOptions::Create(*pServer, pSite, *pHttpApplication, options));
|
||||
// Set the currently running application to a fake application that returns startup exceptions.
|
||||
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, g_hServerModule, options->QueryDisableStartUpErrorPage(), hr);
|
||||
auto pErrorApplication = std::make_unique<StartupExceptionApplication>(*pServer, *pHttpApplication, g_hServerModule, options->QueryDisableStartUpErrorPage(), hr, std::move(g_errorPageContent));
|
||||
|
||||
RETURN_IF_FAILED(pErrorApplication->StartMonitoringAppOffline());
|
||||
*ppApplication = pErrorApplication.release();
|
||||
|
|
|
|||
|
|
@ -235,8 +235,26 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
|||
LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str());
|
||||
}
|
||||
|
||||
bool clrThreadExited;
|
||||
if (m_pConfig->QueryCallStartupHook())
|
||||
{
|
||||
// Used to display developer exception page when there is an exception in main.
|
||||
auto currentStartupHookEnv = Environment::GetEnvironmentVariableValue(DOTNETCORE_STARTUP_HOOK);
|
||||
|
||||
if (currentStartupHookEnv.has_value())
|
||||
{
|
||||
currentStartupHookEnv = currentStartupHookEnv.value() + L";" + ASPNETCORE_STARTUP_ASSEMBLY;
|
||||
LOG_LAST_ERROR_IF(!SetEnvironmentVariable(DOTNETCORE_STARTUP_HOOK, currentStartupHookEnv.value().c_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_LAST_ERROR_IF(!SetEnvironmentVariable(DOTNETCORE_STARTUP_HOOK, ASPNETCORE_STARTUP_ASSEMBLY));
|
||||
}
|
||||
}
|
||||
|
||||
// Used to make .NET Runtime always log to event log when there is an unhandled exception.
|
||||
LOG_LAST_ERROR_IF(SetEnvironmentVariable(L"COMPlus_UseEntryPointFilter", L"1"));
|
||||
|
||||
bool clrThreadExited;
|
||||
{
|
||||
auto redirectionOutput = LoggingHelpers::CreateOutputs(
|
||||
m_pConfig->QueryStdoutLogEnabled(),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext);
|
|||
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion);
|
||||
typedef void(WINAPI * PFN_REQUESTS_DRAINED_HANDLER) (void* pvShutdownHandlerContext);
|
||||
|
||||
#define DOTNETCORE_STARTUP_HOOK L"DOTNET_STARTUP_HOOKS"
|
||||
#define ASPNETCORE_STARTUP_ASSEMBLY L"Microsoft.AspNetCore.Server.IIS"
|
||||
class IN_PROCESS_APPLICATION : public InProcessApplicationBase
|
||||
{
|
||||
public:
|
||||
|
|
@ -58,7 +60,6 @@ public:
|
|||
HRESULT
|
||||
LoadManagedApplication();
|
||||
|
||||
|
||||
void
|
||||
QueueStop();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@
|
|||
#include "inprocessapplication.h"
|
||||
#include "inprocesshandler.h"
|
||||
#include "requesthandler_config.h"
|
||||
#include "EventLog.h"
|
||||
|
||||
extern bool g_fInProcessApplicationCreated;
|
||||
extern std::vector<byte> g_errorPageContent;
|
||||
extern IHttpServer* g_pHttpServer;
|
||||
|
||||
//
|
||||
// Initialization export
|
||||
|
|
@ -236,7 +239,7 @@ http_read_request_bytes(
|
|||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
IHttpRequest *pHttpRequest = (IHttpRequest*)pInProcessHandler->QueryHttpContext()->GetRequest();
|
||||
IHttpRequest* pHttpRequest = (IHttpRequest*)pInProcessHandler->QueryHttpContext()->GetRequest();
|
||||
|
||||
// Check if there is anything to read
|
||||
if (pHttpRequest->GetRemainingEntityBytes() > 0)
|
||||
|
|
@ -267,7 +270,7 @@ http_write_response_bytes(
|
|||
_In_ BOOL* pfCompletionExpected
|
||||
)
|
||||
{
|
||||
IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
IHttpResponse* pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
BOOL fAsync = TRUE;
|
||||
BOOL fMoreData = TRUE;
|
||||
DWORD dwBytesSent = 0;
|
||||
|
|
@ -291,7 +294,7 @@ http_flush_response_bytes(
|
|||
_Out_ BOOL* pfCompletionExpected
|
||||
)
|
||||
{
|
||||
IHttpResponse *pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
IHttpResponse* pHttpResponse = (IHttpResponse*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
|
||||
BOOL fAsync = TRUE;
|
||||
DWORD dwBytesSent = 0;
|
||||
|
|
@ -316,7 +319,7 @@ http_websockets_read_bytes(
|
|||
_In_ BOOL* pfCompletionPending
|
||||
)
|
||||
{
|
||||
IHttpRequest3 *pHttpRequest = (IHttpRequest3*)pInProcessHandler->QueryHttpContext()->GetRequest();
|
||||
IHttpRequest3* pHttpRequest = (IHttpRequest3*)pInProcessHandler->QueryHttpContext()->GetRequest();
|
||||
|
||||
BOOL fAsync = TRUE;
|
||||
|
||||
|
|
@ -343,7 +346,7 @@ http_websockets_write_bytes(
|
|||
_In_ BOOL* pfCompletionExpected
|
||||
)
|
||||
{
|
||||
IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
IHttpResponse2* pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
|
||||
BOOL fAsync = TRUE;
|
||||
BOOL fMoreData = TRUE;
|
||||
|
|
@ -371,7 +374,7 @@ http_websockets_flush_bytes(
|
|||
_In_ BOOL* pfCompletionExpected
|
||||
)
|
||||
{
|
||||
IHttpResponse2 *pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
IHttpResponse2* pHttpResponse = (IHttpResponse2*)pInProcessHandler->QueryHttpContext()->GetResponse();
|
||||
|
||||
BOOL fAsync = TRUE;
|
||||
BOOL fMoreData = TRUE;
|
||||
|
|
@ -504,4 +507,12 @@ set_main_handler(_In_ hostfxr_main_fn main)
|
|||
IN_PROCESS_APPLICATION::SetMainCallback(main);
|
||||
}
|
||||
|
||||
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
|
||||
VOID
|
||||
http_set_startup_error_page_content(_In_ byte* errorPageContent, int length)
|
||||
{
|
||||
g_errorPageContent.resize(length);
|
||||
memcpy(&g_errorPageContent[0], errorPageContent, length);
|
||||
}
|
||||
|
||||
// End of export
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Authentication.Core" />
|
||||
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
<Reference Include="Microsoft.Extensions.TypeNameHelper.Sources" />
|
||||
<Reference Include="System.IO.Pipelines" />
|
||||
<Reference Include="System.Security.Principal.Windows" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -14,8 +14,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)Buffers.MemoryPool\**\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)HttpSys\**\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)Buffers.MemoryPool\**\*.cs" LinkBase="Shared\" />
|
||||
<Compile Include="$(SharedSourceRoot)HttpSys\**\*.cs" LinkBase="Shared\"/>
|
||||
<Compile Include="$(SharedSourceRoot)StackTrace\**\*.cs" LinkBase="Shared\" />
|
||||
<Compile Include="$(SharedSourceRoot)RazorViews\*.cs" LinkBase="Shared\" />
|
||||
<Compile Include="$(SharedSourceRoot)ErrorPage\*.cs" LinkBase="Shared\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="ValidateNativeComponentsBuilt" AfterTargets="Build" >
|
||||
|
|
@ -32,6 +35,8 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Authentication.Core" />
|
||||
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
<Reference Include="Microsoft.Extensions.TypeNameHelper.Sources" />
|
||||
<Reference Include="System.IO.Pipelines" />
|
||||
<Reference Include="System.Security.Principal.Windows" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
[DllImport(AspNetCoreModuleDll)]
|
||||
private static extern int http_get_authentication_information(IntPtr pInProcessHandler, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token);
|
||||
|
||||
[DllImport(AspNetCoreModuleDll)]
|
||||
private static extern unsafe int http_set_startup_error_page_content(byte* content, int contentLength);
|
||||
|
||||
public static void HttpPostCompletion(IntPtr pInProcessHandler, int cbBytes)
|
||||
{
|
||||
Validate(http_post_completion(pInProcessHandler, cbBytes));
|
||||
|
|
@ -169,7 +172,7 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, disconnectCallback, asyncCallback, requestsDrainedHandler, pvRequestContext, pvShutdownContext));
|
||||
}
|
||||
|
||||
public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected)
|
||||
internal static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected)
|
||||
{
|
||||
return http_write_response_bytes(pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
|
||||
}
|
||||
|
|
@ -179,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
return http_flush_response_bytes(pInProcessHandler, fMoreData, out fCompletionExpected);
|
||||
}
|
||||
|
||||
public static unsafe HttpApiTypes.HTTP_REQUEST_V2* HttpGetRawRequest(IntPtr pInProcessHandler)
|
||||
internal static unsafe HttpApiTypes.HTTP_REQUEST_V2* HttpGetRawRequest(IntPtr pInProcessHandler)
|
||||
{
|
||||
return http_get_raw_request(pInProcessHandler);
|
||||
}
|
||||
|
|
@ -219,7 +222,7 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
Validate(http_set_managed_context(pInProcessHandler, pvManagedContext));
|
||||
}
|
||||
|
||||
public static IISConfigurationData HttpGetApplicationProperties()
|
||||
internal static IISConfigurationData HttpGetApplicationProperties()
|
||||
{
|
||||
var iisConfigurationData = new IISConfigurationData();
|
||||
Validate(http_get_application_properties(ref iisConfigurationData));
|
||||
|
|
@ -247,7 +250,7 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
return http_websockets_read_bytes(pInProcessHandler, pvBuffer, cbBuffer, pfnCompletionCallback, pvCompletionContext, out dwBytesReceived, out fCompletionExpected);
|
||||
}
|
||||
|
||||
public static unsafe int HttpWebsocketsWriteBytes(
|
||||
internal static unsafe int HttpWebsocketsWriteBytes(
|
||||
IntPtr pInProcessHandler,
|
||||
HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks,
|
||||
int nChunks,
|
||||
|
|
@ -297,6 +300,14 @@ namespace Microsoft.AspNetCore.Server.IIS
|
|||
Validate(http_get_authentication_information(pInProcessHandler, out authType, out token));
|
||||
}
|
||||
|
||||
internal static unsafe void HttpSetStartupErrorPageContent(byte[] content)
|
||||
{
|
||||
fixed(byte* bytePtr = content)
|
||||
{
|
||||
http_set_startup_error_page_content(bytePtr, content.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Validate(int hr)
|
||||
{
|
||||
if (hr != HR_OK)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Views;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.IIS;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.StackTrace.Sources;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Startup hooks are pieces of code that will run before a users program main executes
|
||||
/// See: https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/host-startup-hook.md
|
||||
/// The type must be named StartupHook without any namespace, and should be internal.
|
||||
/// </summary>
|
||||
internal class StartupHook
|
||||
{
|
||||
/// <summary>
|
||||
/// Startup hooks are pieces of code that will run before a users program main executes
|
||||
/// See: https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/host-startup-hook.md
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!NativeMethods.IsAspNetCoreModuleLoaded())
|
||||
{
|
||||
// This means someone set the startup hook for Microsoft.AspNetCore.Server.IIS
|
||||
// but are not running inprocess. Return at this point.
|
||||
return;
|
||||
}
|
||||
|
||||
var detailedErrors = Environment.GetEnvironmentVariable("ASPNETCORE_DETAILEDERRORS");
|
||||
var enableStartupErrorPage = detailedErrors?.Equals("1", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
enableStartupErrorPage |= detailedErrors?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
|
||||
var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||
enableStartupErrorPage |= aspnetCoreEnvironment?.Equals("Development", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
|
||||
var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
|
||||
enableStartupErrorPage |= dotnetEnvironment?.Equals("Development", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
|
||||
if (!enableStartupErrorPage)
|
||||
{
|
||||
// Not running in development or detailed errors aren't enabled
|
||||
return;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
var exception = (Exception)eventArgs.ExceptionObject;
|
||||
|
||||
// Get the content root from IIS.
|
||||
var iisConfigData = NativeMethods.HttpGetApplicationProperties();
|
||||
var contentRoot = iisConfigData.pwzFullApplicationPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
var model = new ErrorPageModel
|
||||
{
|
||||
RuntimeDisplayName = RuntimeInformation.FrameworkDescription
|
||||
};
|
||||
|
||||
var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly;
|
||||
var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString();
|
||||
var clrVersion = assemblyVersion;
|
||||
model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString();
|
||||
var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly;
|
||||
model.CurrentAssemblyVesion = currentAssembly
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
.InformationalVersion;
|
||||
model.ClrVersion = clrVersion;
|
||||
model.OperatingSystemDescription = RuntimeInformation.OSDescription;
|
||||
|
||||
var exceptionDetailProvider = new ExceptionDetailsProvider(
|
||||
new PhysicalFileProvider(contentRoot),
|
||||
sourceCodeLineCount: 6);
|
||||
|
||||
model.ErrorDetails = exceptionDetailProvider.GetDetails(exception);
|
||||
|
||||
var errorPage = new ErrorPage(model);
|
||||
|
||||
var stream = new MemoryStream();
|
||||
|
||||
// Never will go async because we are writing to a memory stream.
|
||||
errorPage.ExecuteAsync(stream).GetAwaiter().GetResult();
|
||||
|
||||
// Get the raw content and set the error page.
|
||||
stream.Position = 0;
|
||||
var content = stream.ToArray();
|
||||
|
||||
NativeMethods.HttpSetStartupErrorPageContent(content);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
|
@ -481,6 +483,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
|
||||
|
||||
private static Dictionary<string, Func<IISDeploymentParameters, string>> StandaloneConfigTransformations = InitStandaloneConfigTransformations();
|
||||
|
||||
public static IEnumerable<object[]> StandaloneConfigTransformationsScenarios => StandaloneConfigTransformations.ToTheoryData();
|
||||
|
||||
[ConditionalTheory]
|
||||
|
|
@ -572,6 +575,116 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
await request;
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
|
||||
[RequiresNewHandler]
|
||||
[InlineData("ASPNETCORE_ENVIRONMENT", "Development")]
|
||||
[InlineData("DOTNET_ENVIRONMENT", "deVelopment")]
|
||||
[InlineData("ASPNETCORE_DETAILEDERRORS", "1")]
|
||||
[InlineData("ASPNETCORE_DETAILEDERRORS", "TRUE")]
|
||||
public async Task ExceptionIsLoggedToEventLogAndPutInResponseWhenDeveloperExceptionPageIsEnabled(string environmentVariable, string value)
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} Throw");
|
||||
|
||||
// Deployment parameters by default set ASPNETCORE_DETAILEDERRORS to true
|
||||
deploymentParameters.EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "";
|
||||
deploymentParameters.EnvironmentVariables[environmentVariable] = value;
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
var result = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.False(result.IsSuccessStatusCode);
|
||||
|
||||
var content = await result.Content.ReadAsStringAsync();
|
||||
Assert.Contains("InvalidOperationException", content);
|
||||
Assert.Contains("TestSite.Program.Main", content);
|
||||
|
||||
StopServer();
|
||||
|
||||
VerifyDotnetRuntimeEventLog(deploymentResult);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
|
||||
[RequiresNewHandler]
|
||||
public async Task ExceptionIsNotLoggedToResponseWhenStartupHookIsDisabled()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} Throw");
|
||||
deploymentParameters.EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "";
|
||||
deploymentParameters.EnvironmentVariables["ASPNETCORE_ENVIRONMENT"] = "Development";
|
||||
deploymentParameters.HandlerSettings["callStartupHook"] = "false";
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
var result = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.False(result.IsSuccessStatusCode);
|
||||
|
||||
var content = await result.Content.ReadAsStringAsync();
|
||||
Assert.DoesNotContain("InvalidOperationException", content);
|
||||
|
||||
StopServer();
|
||||
|
||||
VerifyDotnetRuntimeEventLog(deploymentResult);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[RequiresNewHandler]
|
||||
public async Task ExceptionIsLoggedToEventLogDoesNotWriteToResponse()
|
||||
{
|
||||
var deploymentParameters = Fixture.GetBaseDeploymentParameters();
|
||||
deploymentParameters.TransformArguments((a, _) => $"{a} Throw");
|
||||
|
||||
// Deployment parameters by default set ASPNETCORE_DETAILEDERRORS to true
|
||||
deploymentParameters.EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "";
|
||||
|
||||
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||
var result = await deploymentResult.HttpClient.GetAsync("/");
|
||||
Assert.False(result.IsSuccessStatusCode);
|
||||
|
||||
var content = await result.Content.ReadAsStringAsync();
|
||||
Assert.DoesNotContain("InvalidOperationException", content);
|
||||
|
||||
StopServer();
|
||||
|
||||
VerifyDotnetRuntimeEventLog(deploymentResult);
|
||||
}
|
||||
|
||||
private static void VerifyDotnetRuntimeEventLog(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
var entries = GetEventLogsFromDotnetRuntime(deploymentResult);
|
||||
|
||||
var expectedRegex = new Regex("Exception Info: System\\.InvalidOperationException:", RegexOptions.Singleline);
|
||||
var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
|
||||
// There isn't a process ID to filter on here, so there can be duplicate entries from other tests.
|
||||
Assert.True(matchedEntries.Length > 0);
|
||||
}
|
||||
|
||||
private static IEnumerable<EventLogEntry> GetEventLogsFromDotnetRuntime(IISDeploymentResult deploymentResult)
|
||||
{
|
||||
var processStartTime = deploymentResult.HostProcess.StartTime.AddSeconds(-5);
|
||||
var eventLog = new EventLog("Application");
|
||||
|
||||
for (var i = eventLog.Entries.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var eventLogEntry = eventLog.Entries[i];
|
||||
if (eventLogEntry.TimeGenerated < processStartTime)
|
||||
{
|
||||
// If event logs is older than the process start time, we didn't find a match.
|
||||
break;
|
||||
}
|
||||
|
||||
if (eventLogEntry.ReplacementStrings == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eventLogEntry.Source == ".NET Runtime")
|
||||
{
|
||||
yield return eventLogEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MoveApplication(
|
||||
IISDeploymentParameters parameters,
|
||||
string subdirectory)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
|
|||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,4 +1,4 @@
|
|||
@using System
|
||||
@using System
|
||||
@using System.Globalization
|
||||
@using System.Linq
|
||||
@using System.Net
|
||||
|
|
@ -15,20 +15,19 @@
|
|||
public ErrorPageModel Model { get; set; }
|
||||
}
|
||||
@{
|
||||
Response.ContentType = "text/html; charset=utf-8";
|
||||
var location = string.Empty;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>@Resources.ErrorPageHtml_Title</title>
|
||||
<title>Internal Server Error</title>
|
||||
<style>
|
||||
<%$ include: ErrorPage.css %>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>@Resources.ErrorPageHtml_UnhandledException</h1>
|
||||
<h1>An error occurred while starting the application.</h1>
|
||||
@foreach (var errorDetail in Model.ErrorDetails)
|
||||
{
|
||||
<div class="titleerror">@errorDetail.Error.GetType().Name: @{ Output.Write(HtmlEncodeAndReplaceLineBreaks(errorDetail.Error.Message)); }</div>
|
||||
|
|
@ -49,7 +48,7 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
<p class="location">@Resources.ErrorPageHtml_UnknownLocation</p>
|
||||
<p class="location">Unknown location</p>
|
||||
}
|
||||
|
||||
var reflectionTypeLoadException = errorDetail.Error as ReflectionTypeLoadException;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -57,6 +57,23 @@ namespace Microsoft.Extensions.RazorViews
|
|||
/// </summary>
|
||||
protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Execute an individual request
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write to</param>
|
||||
public async Task ExecuteAsync(Stream stream)
|
||||
{
|
||||
// We technically don't need this intermediate buffer if this method accepts a memory stream.
|
||||
var buffer = new MemoryStream();
|
||||
Output = new StreamWriter(buffer, UTF8NoBOM, 4096, leaveOpen: true);
|
||||
await ExecuteAsync();
|
||||
await Output.FlushAsync();
|
||||
Output.Dispose();
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
await buffer.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an individual request
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
|
|
|
|||
Loading…
Reference in New Issue