Display startup errors in ANCM (#8518)

This commit is contained in:
Pavel Krymets 2019-04-18 13:48:48 -07:00 committed by Justin Kotalik
parent eb41de88a9
commit 56c064bd53
27 changed files with 1408 additions and 1115 deletions

View File

@ -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);
};
}

View File

@ -12,6 +12,7 @@
<ItemGroup>
<Compile Include="$(SharedSourceRoot)RazorViews\*.cs" />
<Compile Include="$(SharedSourceRoot)StackTrace\**\*.cs" />
<Compile Include="$(SharedSourceRoot)ErrorPage\**\*.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -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>

View File

@ -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
{

View File

@ -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;
};

View File

@ -13,6 +13,7 @@ using ::testing::NiceMock;
// Externals defined in inprocess
BOOL g_fProcessDetach;
HANDLE g_hEventLog;
std::wstring g_exceptionEventLog;
namespace InprocessTests
{

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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(),

View File

@ -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();

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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);
};
}
}

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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;