Add client disconnect and connection abort support (#1388)

This commit is contained in:
Pavel Krymets 2018-09-18 16:36:52 -07:00 committed by GitHub
parent ece5ad36e2
commit b6c311c14d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 2021 additions and 349 deletions

View File

@ -36,8 +36,8 @@
<IISExpressArguments>/config:"$(IISExpressAppHostConfig)" /systray:false</IISExpressArguments>
<IISArguments>-h "$(IISAppHostConfig)"</IISArguments>
<AncmPath>$(NativePlatform)\aspnetcore.dll</AncmPath>
<AncmV2Path>$(NativePlatform)\aspnetcorev2.dll</AncmV2Path>
<AncmPath>$(AspNetCoreModuleV1ShimDll)</AncmPath>
<AncmV2Path>$(AspNetCoreModuleV2ShimDll)</AncmV2Path>
<AncmInProcessRHPath>aspnetcorev2_inprocess.dll</AncmInProcessRHPath>
<DotNetPath>$(userprofile)\.dotnet\$(NativePlatform)\dotnet.exe</DotNetPath>
</PropertyGroup>

View File

@ -231,6 +231,7 @@
<ClInclude Include="applicationinfo.h" />
<ClInclude Include="AppOfflineApplication.h" />
<ClInclude Include="AppOfflineHandler.h" />
<ClInclude Include="DisconnectHandler.h" />
<ClInclude Include="ShimOptions.h" />
<ClInclude Include="globalmodule.h" />
<ClInclude Include="applicationmanager.h" />
@ -246,6 +247,7 @@
<ClCompile Include="applicationmanager.cpp" />
<ClCompile Include="AppOfflineApplication.cpp" />
<ClCompile Include="AppOfflineHandler.cpp" />
<ClCompile Include="DisconnectHandler.cpp" />
<ClCompile Include="ShimOptions.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="globalmodule.cpp" />

View File

@ -0,0 +1,33 @@
// 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.
#include "DisconnectHandler.h"
#include "exceptions.h"
#include "proxymodule.h"
void DisconnectHandler::NotifyDisconnect()
{
try
{
const auto module = m_pModule.exchange(nullptr);
if (module != nullptr)
{
module ->NotifyDisconnect();
}
}
catch (...)
{
OBSERVE_CAUGHT_EXCEPTION();
}
}
void DisconnectHandler::CleanupStoredContext() noexcept
{
SetHandler(nullptr);
delete this;
}
void DisconnectHandler::SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept
{
m_pModule = module;
}

View File

@ -0,0 +1,35 @@
// 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
#include <atomic>
class ASPNET_CORE_PROXY_MODULE;
class DisconnectHandler final: public IHttpConnectionStoredContext
{
public:
DisconnectHandler()
: m_pModule(nullptr)
{
}
virtual
~DisconnectHandler()
{
SetHandler(nullptr);
}
void
NotifyDisconnect() override;
void
CleanupStoredContext() noexcept override;
void
SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept;
private:
std::atomic<ASPNET_CORE_PROXY_MODULE*> m_pModule;
};

View File

@ -41,7 +41,7 @@ BOOL WINAPI DllMain(HMODULE hModule,
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
ALLOC_CACHE_HANDLER::StaticInitialize();
g_hServerModule = hModule;
DisableThreadLibraryCalls(hModule);
@ -101,7 +101,7 @@ HRESULT
{
g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER);
}
// check whether the feature is disabled due to security reason
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
@ -144,9 +144,9 @@ HRESULT
// The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any
// static object initialized.
//
auto applicationManager = std::make_shared<APPLICATION_MANAGER>(g_hServerModule, *pHttpServer);
auto moduleFactory = std::make_unique<ASPNET_CORE_PROXY_MODULE_FACTORY>(applicationManager);
auto moduleFactory = std::make_unique<ASPNET_CORE_PROXY_MODULE_FACTORY>(pModuleInfo->GetId(), applicationManager);
RETURN_IF_FAILED(pModuleInfo->SetRequestNotifications(
moduleFactory.release(),
@ -159,7 +159,7 @@ HRESULT
pGlobalModule.release(),
GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop
GL_STOP_LISTENING)); // worker process stop or recycle
return S_OK;
}
CATCH_RETURN()

View File

@ -6,13 +6,15 @@
#include "applicationmanager.h"
#include "applicationinfo.h"
#include "exceptions.h"
#include "DisconnectHandler.h"
extern BOOL g_fInShutdown;
__override
ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept
:m_pApplicationManager(std::move(applicationManager))
ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept
: m_pApplicationManager(std::move(applicationManager)),
m_moduleId(moduleId)
{
}
@ -22,10 +24,10 @@ ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule(
IModuleAllocator * pAllocator
)
{
#pragma warning( push )
#pragma warning ( disable : 26409 ) // Disable "Avoid using new"
*ppModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(m_pApplicationManager);
*ppModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(m_moduleId, m_pApplicationManager);
#pragma warning( push )
if (*ppModule == nullptr)
{
@ -58,13 +60,23 @@ Return value:
delete this;
}
ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept
ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept
: m_pApplicationManager(std::move(applicationManager)),
m_pApplicationInfo(nullptr),
m_pHandler(nullptr)
m_pHandler(nullptr),
m_moduleId(moduleId),
m_pDisconnectHandler(nullptr)
{
}
ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE()
{
if (m_pDisconnectHandler != nullptr)
{
m_pDisconnectHandler->SetHandler(nullptr);
}
}
__override
REQUEST_NOTIFICATION_STATUS
ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
@ -77,12 +89,31 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
try
{
if (g_fInShutdown)
{
FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS));
}
auto moduleContainer = pHttpContext
->GetConnection()
->GetModuleContextContainer();
#pragma warning( push )
#pragma warning ( disable : 26466 ) // Disable "Don't use static_cast downcasts". We build without RTTI support so dynamic_cast is not available
m_pDisconnectHandler = static_cast<DisconnectHandler*>(moduleContainer->GetConnectionModuleContext(m_moduleId));
#pragma warning( push )
if (m_pDisconnectHandler == nullptr)
{
auto disconnectHandler = std::make_unique<DisconnectHandler>();
m_pDisconnectHandler = disconnectHandler.get();
// ModuleContextContainer takes ownership of disconnectHandler
// we are trusting that it would not release it before deleting the context
FINISHED_IF_FAILED(moduleContainer->SetConnectionModuleContext(static_cast<IHttpConnectionStoredContext*>(disconnectHandler.release()), m_moduleId));
}
m_pDisconnectHandler->SetHandler(this);
FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo(
*pHttpContext,
m_pApplicationInfo));
@ -135,3 +166,8 @@ ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion(
return RQ_NOTIFICATION_FINISH_REQUEST;
}
}
void ASPNET_CORE_PROXY_MODULE::NotifyDisconnect() const
{
m_pHandler->NotifyDisconnect();
}

View File

@ -7,6 +7,7 @@
#include "applicationinfo.h"
#include "irequesthandler.h"
#include "applicationmanager.h"
#include "DisconnectHandler.h"
extern HTTP_MODULE_ID g_pModuleId;
@ -14,9 +15,9 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
{
public:
ASPNET_CORE_PROXY_MODULE(std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept;
ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept;
~ASPNET_CORE_PROXY_MODULE() = default;
~ASPNET_CORE_PROXY_MODULE();
void * operator new(size_t size, IModuleAllocator * pPlacement)
{
@ -46,16 +47,21 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
IHttpCompletionInfo * pCompletionInfo
) override;
void
NotifyDisconnect() const;
private:
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
std::shared_ptr<APPLICATION_INFO> m_pApplicationInfo;
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> m_pHandler;
HTTP_MODULE_ID m_moduleId;
DisconnectHandler * m_pDisconnectHandler;
};
class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
{
public:
ASPNET_CORE_PROXY_MODULE_FACTORY(std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept;
ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept;
virtual ~ASPNET_CORE_PROXY_MODULE_FACTORY() = default;
HRESULT
@ -66,7 +72,8 @@ class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
VOID
Terminate() noexcept override;
private:
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
HTTP_MODULE_ID m_moduleId;
};

View File

@ -26,9 +26,7 @@ public:
virtual
VOID
TerminateRequest(
bool fClientInitiated
) = 0;
NotifyDisconnect() noexcept(false) = 0;
virtual
~IREQUEST_HANDLER(

View File

@ -14,7 +14,7 @@ class REQUEST_HANDLER: public virtual IREQUEST_HANDLER
public:
VOID
ReferenceRequestHandler() noexcept override
ReferenceRequestHandler() noexcept override
{
InterlockedIncrement(&m_cRefs);
}
@ -39,9 +39,11 @@ public:
return RQ_NOTIFICATION_FINISH_REQUEST;
}
VOID TerminateRequest(bool fClientInitiated) override
#pragma warning( push )
#pragma warning ( disable : 26440 ) // Disable "Can be marked with noexcept"
VOID NotifyDisconnect() override
#pragma warning( pop )
{
UNREFERENCED_PARAMETER(fClientInitiated);
}
private:

View File

@ -44,14 +44,14 @@ IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
VOID
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
{
{
StopClr();
InProcessApplicationBase::StopInternal(fServerInitiated);
}
VOID
IN_PROCESS_APPLICATION::StopClr()
{
{
LOG_INFO(L"Stopping CLR");
if (!m_blockManagedCallbacks)
@ -86,6 +86,7 @@ VOID
IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
_In_ PFN_DISCONNECT_HANDLER disconnect_callback,
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@ -95,6 +96,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext;
m_DisconnectHandler = disconnect_callback;
m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler;
@ -135,13 +137,13 @@ IN_PROCESS_APPLICATION::LoadManagedApplication()
}, ::ReferenceApplication(this));
LOG_INFO(L"Waiting for initialization");
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
if (waitResult == WAIT_TIMEOUT)
{
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
@ -168,9 +170,9 @@ void
IN_PROCESS_APPLICATION::ExecuteApplication()
{
try
{
{
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
auto context = std::make_shared<ExecuteClrContext>();
auto pProc = s_fMainCallback;
@ -184,7 +186,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// Get the entry point for main
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
THROW_LAST_ERROR_IF_NULL(pProc);
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
m_dotnetExeKnownLocation,
m_pConfig->QueryProcessPath(),
@ -217,7 +219,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
//Start CLR thread
m_clrThread = std::thread(ClrThreadEntryPoint, context);
@ -237,7 +239,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
{
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
}
@ -274,7 +276,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// in case when it was not initialized we need to keep server running to serve 502 page
if (m_Initialized)
{
QueueStop();
QueueStop();
}
}
}
@ -298,7 +300,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
GetUnexpectedExceptionMessage(ex).c_str());
OBSERVE_CAUGHT_EXCEPTION();
}
}
@ -500,9 +502,9 @@ IN_PROCESS_APPLICATION::CreateHandler(
{
try
{
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
}
CATCH_RETURN();
return S_OK;
}

View File

@ -10,6 +10,7 @@
class IN_PROCESS_HANDLER;
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
typedef VOID(WINAPI * PFN_DISCONNECT_HANDLER) (void *pvManagedHttpContext);
typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext);
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion);
@ -33,6 +34,7 @@ public:
SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_callback,
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
_In_ PFN_DISCONNECT_HANDLER disconnect_callback,
_In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@ -48,14 +50,14 @@ public:
// Executes the .NET Core process
void
ExecuteApplication();
HRESULT
LoadManagedApplication();
void
QueueStop();
void
StopIncomingRequests()
{
@ -110,7 +112,7 @@ public:
private:
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
{
ExecuteClrContext():
ExecuteClrContext():
m_argc(0),
m_pProc(nullptr),
m_exitCode(0),
@ -121,11 +123,11 @@ private:
DWORD m_argc;
std::unique_ptr<PCWSTR[]> m_argv;
hostfxr_main_fn m_pProc;
int m_exitCode;
int m_exceptionCode;
};
// Thread executing the .NET Core process this might be abandoned in timeout cases
std::thread m_clrThread;
// Thread tracking the CLR thread, this one is always joined on shutdown
@ -144,6 +146,7 @@ private:
VOID* m_ShutdownHandlerContext;
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
PFN_DISCONNECT_HANDLER m_DisconnectHandler;
std::wstring m_dotnetExeKnownLocation;
@ -164,7 +167,7 @@ private:
HRESULT
SetEnvironmentVariablesOnWorkerProcess();
void
StopClr();
@ -175,7 +178,7 @@ private:
static
void
ExecuteClr(const std::shared_ptr<ExecuteClrContext> &context);
// Allows to override call to hostfxr_main with custom callback
// used in testing
inline static hostfxr_main_fn s_fMainCallback = nullptr;

View File

@ -6,6 +6,7 @@
#include "aspnetcore_event.h"
#include "IOutputManager.h"
#include "ShuttingDownApplication.h"
#include "ntassert.h"
ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL;
@ -14,6 +15,7 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
_In_ IHttpContext *pW3Context,
_In_ PFN_REQUEST_HANDLER pRequestHandler,
_In_ void * pRequestHandlerContext,
_In_ PFN_DISCONNECT_HANDLER pDisconnectHandler,
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion
): m_pManagedHttpContext(nullptr),
m_requestNotificationStatus(RQ_NOTIFICATION_PENDING),
@ -22,7 +24,8 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
m_pApplication(std::move(pApplication)),
m_pRequestHandler(pRequestHandler),
m_pRequestHandlerContext(pRequestHandlerContext),
m_pAsyncCompletionHandler(pAsyncCompletion)
m_pAsyncCompletionHandler(pAsyncCompletion),
m_pDisconnectHandler(pDisconnectHandler)
{
}
@ -63,7 +66,7 @@ IN_PROCESS_HANDLER::OnExecuteRequestHandler()
{
return ServerShutdownMessage();
}
return m_pRequestHandler(this, m_pRequestHandlerContext);
}
@ -87,6 +90,7 @@ IN_PROCESS_HANDLER::OnAsyncCompletion(
return ServerShutdownMessage();
}
assert(m_pManagedHttpContext != nullptr);
// Call the managed handler for async completion.
return m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion);
}
@ -97,11 +101,16 @@ REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const
}
VOID
IN_PROCESS_HANDLER::TerminateRequest(
bool fClientInitiated
)
IN_PROCESS_HANDLER::NotifyDisconnect()
{
UNREFERENCED_PARAMETER(fClientInitiated);
if (m_pApplication->QueryBlockCallbacksIntoManaged() ||
m_fManagedRequestComplete)
{
return;
}
assert(m_pManagedHttpContext != nullptr);
m_pDisconnectHandler(m_pManagedHttpContext);
}
VOID
@ -110,6 +119,7 @@ IN_PROCESS_HANDLER::IndicateManagedRequestComplete(
)
{
m_fManagedRequestComplete = TRUE;
m_pManagedHttpContext = nullptr;
}
VOID

View File

@ -18,6 +18,7 @@ public:
_In_ IHttpContext *pW3Context,
_In_ PFN_REQUEST_HANDLER pRequestHandler,
_In_ void * pRequestHandlerContext,
_In_ PFN_DISCONNECT_HANDLER m_DisconnectHandler,
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion);
~IN_PROCESS_HANDLER() override = default;
@ -35,10 +36,8 @@ public:
__override
VOID
TerminateRequest(
bool fClientInitiated
) override;
NotifyDisconnect() override;
IHttpContext*
QueryHttpContext() const
{
@ -82,6 +81,6 @@ private:
PFN_REQUEST_HANDLER m_pRequestHandler;
void* m_pRequestHandlerContext;
PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler;
PFN_DISCONNECT_HANDLER m_pDisconnectHandler;
static ALLOC_CACHE_HANDLER * sm_pAlloc;
};

View File

@ -16,6 +16,7 @@ register_callbacks(
_In_ IN_PROCESS_APPLICATION* pInProcessApplication,
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
_In_ PFN_DISCONNECT_HANDLER disconnect_handler,
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@ -29,6 +30,7 @@ register_callbacks(
pInProcessApplication->SetCallbackHandles(
request_handler,
shutdown_handler,
disconnect_handler,
async_completion_handler,
pvRequstHandlerContext,
pvShutdownHandlerContext
@ -424,6 +426,16 @@ http_disable_buffering(
return S_OK;
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_close_connection(
_In_ IN_PROCESS_HANDLER* pInProcessHandler
)
{
pInProcessHandler->QueryHttpContext()->GetResponse()->ResetConnection();
return S_OK;
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_response_set_unknown_header(

View File

@ -224,7 +224,6 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="disconnectcontext.h" />
<ClInclude Include="environmentvariablehelpers.h" />
<ClInclude Include="forwarderconnection.h" />
<ClInclude Include="processmanager.h" />

View File

@ -1,78 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext
{
public:
ASYNC_DISCONNECT_CONTEXT()
{
m_pHandler = NULL;
}
VOID
CleanupStoredContext()
{
DBG_ASSERT(m_pHandler == NULL);
delete this;
}
VOID
NotifyDisconnect()
{
IREQUEST_HANDLER *pInitialValue = (IREQUEST_HANDLER*)
InterlockedExchangePointer((PVOID*)&m_pHandler, NULL);
if (pInitialValue != NULL)
{
pInitialValue->TerminateRequest(TRUE);
pInitialValue->DereferenceRequestHandler();
}
}
VOID
SetHandler(
IREQUEST_HANDLER *pHandler
)
{
//
// Take a reference on the forwarding handler.
// This reference will be released on either of two conditions:
//
// 1. When the request processing ends, in which case a ResetHandler()
// is called.
//
// 2. When a disconnect notification arrives.
//
// We need to make sure that only one of them ends up dereferencing
// the object.
//
DBG_ASSERT(pHandler != NULL);
DBG_ASSERT(m_pHandler == NULL);
pHandler->ReferenceRequestHandler();
InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler);
}
VOID
ResetHandler(
VOID
)
{
IREQUEST_HANDLER *pInitialValue = (IREQUEST_HANDLER*)
InterlockedExchangePointer((PVOID*)&m_pHandler, NULL);
if (pInitialValue != NULL)
{
pInitialValue->DereferenceRequestHandler();
}
}
private:
~ASYNC_DISCONNECT_CONTEXT()
{}
IREQUEST_HANDLER * m_pHandler;
};

View File

@ -46,7 +46,8 @@ FORWARDING_HANDLER::FORWARDING_HANDLER(
m_fServerResetConn(FALSE),
m_cRefs(1),
m_pW3Context(pW3Context),
m_pApplication(std::move(pApplication))
m_pApplication(std::move(pApplication)),
m_fReactToDisconnect(FALSE)
{
LOG_TRACE(L"FORWARDING_HANDLER::FORWARDING_HANDLER");
@ -73,7 +74,7 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER(
// The m_pServer cleanup would happen afterwards, since there may be a
// call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders()
//
DBG_ASSERT(m_pDisconnect == NULL);
DBG_ASSERT(!m_fReactToDisconnect);
RemoveRequest();
@ -93,7 +94,6 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE;
HRESULT hr = S_OK;
BOOL fRequestLocked = FALSE;
BOOL fHandleSet = FALSE;
BOOL fFailedToStartKestrel = FALSE;
BOOL fSecure = FALSE;
HINTERNET hConnect = NULL;
@ -199,31 +199,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
goto Failure;
}
// Set client disconnect callback contract with IIS
m_pDisconnect = static_cast<ASYNC_DISCONNECT_CONTEXT *>(
pClientConnection->GetModuleContextContainer()->
GetConnectionModuleContext(m_pModuleId));
if (m_pDisconnect == NULL)
{
m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT();
if (m_pDisconnect == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
}
hr = pClientConnection->GetModuleContextContainer()->
SetConnectionModuleContext(m_pDisconnect,
m_pModuleId);
DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED));
if (FAILED_LOG(hr))
{
goto Failure;
}
}
m_pDisconnect->SetHandler(this);
fHandleSet = TRUE;
m_fReactToDisconnect = TRUE;
// require lock as client disconnect callback may happen
AcquireSRWLockShared(&m_RequestLock);
@ -2705,21 +2681,16 @@ FORWARDING_HANDLER::RemoveRequest(
VOID
)
{
ASYNC_DISCONNECT_CONTEXT * pDisconnect;
pDisconnect = (ASYNC_DISCONNECT_CONTEXT *)InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL);
if (pDisconnect != NULL)
{
pDisconnect->ResetHandler();
pDisconnect = NULL;
}
m_fReactToDisconnect = FALSE;
}
VOID
FORWARDING_HANDLER::TerminateRequest(
bool fClientInitiated
)
FORWARDING_HANDLER::NotifyDisconnect()
{
UNREFERENCED_PARAMETER(fClientInitiated);
if (!m_fReactToDisconnect)
{
return;
}
BOOL fLocked = FALSE;
if (TlsGetValue(g_dwTlsIndex) != this)
@ -2740,7 +2711,7 @@ FORWARDING_HANDLER::TerminateRequest(
if (!m_fHttpHandleInClose)
{
m_fClientDisconnected = fClientInitiated;
m_fClientDisconnected = true;
}
if (fLocked)

View File

@ -68,9 +68,7 @@ public:
StaticTerminate();
VOID
TerminateRequest(
bool fClientInitiated
);
NotifyDisconnect() override;
static void * operator new(size_t size);
@ -220,7 +218,6 @@ private:
DWORD m_cMinBufferLimit;
ULONGLONG m_cContentLength;
WEBSOCKET_HANDLER * m_pWebSocket;
ASYNC_DISCONNECT_CONTEXT * m_pDisconnect;
BYTE * m_pEntityBuffer;
static const SIZE_T INLINE_ENTITY_BUFFERS = 8;
@ -239,5 +236,5 @@ private:
mutable LONG m_cRefs;
IHttpContext* m_pW3Context;
std::unique_ptr<OUT_OF_PROCESS_APPLICATION, IAPPLICATION_DELETER> m_pApplication;
HTTP_MODULE_ID m_pModuleId;
bool m_fReactToDisconnect;
};

View File

@ -59,7 +59,6 @@
#include "resources.h"
#include "aspnetcore_event.h"
#include "aspnetcore_msg.h"
#include "disconnectcontext.h"
#include "requesthandler_config.h"
#include "sttimer.h"

View File

@ -8,7 +8,6 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
// TODO redudant file, remove
// See https://github.com/aspnet/IISIntegration/issues/426
internal class DuplexStream : Stream
{

View File

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class EmptyStream : ReadOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private HttpStreamState _state;
private Exception _error;
public EmptyStream(IHttpBodyControlFeature bodyControl)
{
_bodyControl = bodyControl;
_state = HttpStreamState.Open;
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
}
return 0;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return Task.FromResult(0);
}
public void StopAcceptingReads()
{
// Can't use dispose (or close) as can be disposed too early by user code
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
_state = HttpStreamState.Closed;
}
public void Abort(Exception error = null)
{
// We don't want to throw an ODE until the app func actually completes.
// If the request is aborted, we throw a TaskCanceledException instead,
// unless error is not null, in which case we throw it.
if (_state != HttpStreamState.Closed)
{
_state = HttpStreamState.Aborted;
_error = error;
}
}
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpRequestStream));
case HttpStreamState.Aborted:
if (_error != null)
{
ExceptionDispatchInfo.Capture(_error).Throw();
}
else
{
throw new TaskCanceledException();
}
break;
}
}
}
}

View File

@ -0,0 +1,160 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class HttpRequestStream : ReadOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private IISHttpContext _body;
private HttpStreamState _state;
private Exception _error;
public HttpRequestStream(IHttpBodyControlFeature bodyControl)
{
_bodyControl = bodyControl;
_state = HttpStreamState.Closed;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
}
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = ReadAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override int EndRead(IAsyncResult asyncResult)
{
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
}
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<int>(state);
var task = ReadAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<int>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(task2.Result);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return ReadAsyncInternal(destination, cancellationToken);
}
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
private async ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
{
try
{
return await _body.ReadAsync(buffer, cancellationToken);
}
catch (ConnectionAbortedException ex)
{
throw new TaskCanceledException("The request was aborted", ex);
}
}
public void StartAcceptingReads(IISHttpContext body)
{
// Only start if not aborted
if (_state == HttpStreamState.Closed)
{
_state = HttpStreamState.Open;
_body = body;
}
}
public void StopAcceptingReads()
{
// Can't use dispose (or close) as can be disposed too early by user code
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
_state = HttpStreamState.Closed;
_body = null;
}
public void Abort(Exception error = null)
{
// We don't want to throw an ODE until the app func actually completes.
// If the request is aborted, we throw a TaskCanceledException instead,
// unless error is not null, in which case we throw it.
if (_state != HttpStreamState.Closed)
{
_state = HttpStreamState.Aborted;
_error = error;
}
}
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpRequestStream));
case HttpStreamState.Aborted:
if (_error != null)
{
ExceptionDispatchInfo.Capture(_error).Throw();
}
else
{
throw new TaskCanceledException();
}
break;
}
}
}
}

View File

@ -0,0 +1,151 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class HttpResponseStream : WriteOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private readonly IISHttpContext _context;
private HttpStreamState _state;
public HttpResponseStream(IHttpBodyControlFeature bodyControl, IISHttpContext context)
{
_bodyControl = bodyControl;
_context = context;
_state = HttpStreamState.Closed;
}
public override void Flush()
{
FlushAsync(default(CancellationToken)).GetAwaiter().GetResult();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return _context.FlushAsync(cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
}
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = WriteAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override void EndWrite(IAsyncResult asyncResult)
{
((Task<object>)asyncResult).GetAwaiter().GetResult();
}
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<object>(state);
var task = WriteAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<object>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(null);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return _context.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return new ValueTask(_httpResponseControl.WriteAsync(source, cancellationToken));
}
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
public void StartAcceptingWrites()
{
// Only start if not aborted
if (_state == HttpStreamState.Closed)
{
_state = HttpStreamState.Open;
}
}
public void StopAcceptingWrites()
{
// Can't use dispose (or close) as can be disposed too early by user code
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
_state = HttpStreamState.Closed;
}
public void Abort()
{
// We don't want to throw an ODE until the app func actually completes.
if (_state != HttpStreamState.Closed)
{
_state = HttpStreamState.Aborted;
}
}
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpResponseStream), CoreStrings.WritingToResponseBodyAfterResponseCompleted);
case HttpStreamState.Aborted:
if (cancellationToken.IsCancellationRequested)
{
// Aborted state only throws on write if cancellationToken requests it
cancellationToken.ThrowIfCancellationRequested();
}
break;
}
}
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNetCore.Server.IIS.Core
{
enum HttpStreamState
{
Open,
Closed,
Aborted
}
}

View File

@ -0,0 +1,208 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class HttpUpgradeStream : Stream
{
private readonly Stream _requestStream;
private readonly Stream _responseStream;
public HttpUpgradeStream(Stream requestStream, Stream responseStream)
{
_requestStream = requestStream;
_responseStream = responseStream;
}
public override bool CanRead
{
get
{
return _requestStream.CanRead;
}
}
public override bool CanSeek
{
get
{
return _requestStream.CanSeek;
}
}
public override bool CanTimeout
{
get
{
return _responseStream.CanTimeout || _requestStream.CanTimeout;
}
}
public override bool CanWrite
{
get
{
return _responseStream.CanWrite;
}
}
public override long Length
{
get
{
return _requestStream.Length;
}
}
public override long Position
{
get
{
return _requestStream.Position;
}
set
{
_requestStream.Position = value;
}
}
public override int ReadTimeout
{
get
{
return _requestStream.ReadTimeout;
}
set
{
_requestStream.ReadTimeout = value;
}
}
public override int WriteTimeout
{
get
{
return _responseStream.WriteTimeout;
}
set
{
_responseStream.WriteTimeout = value;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_requestStream.Dispose();
_responseStream.Dispose();
}
}
public override void Flush()
{
_responseStream.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _responseStream.FlushAsync(cancellationToken);
}
public override void Close()
{
_requestStream.Close();
_responseStream.Close();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _requestStream.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _requestStream.EndRead(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _responseStream.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
_responseStream.EndWrite(asyncResult);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _requestStream.ReadAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return _requestStream.ReadAsync(destination, cancellationToken);
}
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
return _requestStream.CopyToAsync(destination, bufferSize, cancellationToken);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _responseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
return _responseStream.WriteAsync(source, cancellationToken);
}
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
public override long Seek(long offset, SeekOrigin origin)
{
return _requestStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_requestStream.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
return _requestStream.Read(buffer, offset, count);
}
public override int ReadByte()
{
return _requestStream.ReadByte();
}
public override void Write(byte[] buffer, int offset, int count)
{
_responseStream.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
_responseStream.WriteByte(value);
}
}
}

View File

@ -27,7 +27,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
IHttpAuthenticationFeature,
IServerVariablesFeature,
IHttpBufferingFeature,
ITlsConnectionFeature
ITlsConnectionFeature,
IHttpBodyControlFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@ -37,6 +38,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private X509Certificate2 _certificate;
private List<KeyValuePair<Type, object>> MaybeExtra;
public void ResetFeatureCollection()
{
Initialize();
@ -179,12 +181,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
set => ResponseBody = value;
}
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
{
get => RequestAborted;
set => RequestAborted = value;
}
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
bool IHttpUpgradeFeature.IsUpgradableRequest => true;
@ -259,19 +255,18 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
{
// TODO fix these exceptions strings
if (!((IHttpUpgradeFeature)this).IsUpgradableRequest)
{
throw new InvalidOperationException("CoreStrings.CannotUpgradeNonUpgradableRequest");
throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest);
}
if (_wasUpgraded)
{
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes);
}
if (HasResponseStarted)
{
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes);
}
_wasUpgraded = true;
@ -291,7 +286,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
await InitializeResponse(flushHeaders: true);
return new DuplexStream(RequestBody, ResponseBody);
return _streams.Upgrade();
}
Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
@ -327,10 +322,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
void IHttpRequestLifetimeFeature.Abort()
{
Abort();
}
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true;
void IHttpBufferingFeature.DisableRequestBuffering()
{

View File

@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal partial class IISHttpContext : IHttpRequestLifetimeFeature
{
private CancellationTokenSource _abortedCts;
private CancellationToken? _manuallySetRequestAbortToken;
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
{
get
{
// If a request abort token was previously explicitly set, return it.
if (_manuallySetRequestAbortToken.HasValue)
{
return _manuallySetRequestAbortToken.Value;
}
// Otherwise, get the abort CTS. If we have one, which would mean that someone previously
// asked for the RequestAborted token, simply return its token. If we don't,
// check to see whether we've already aborted, in which case just return an
// already canceled token. Finally, force a source into existence if we still
// don't have one, and return its token.
var cts = _abortedCts;
return
cts != null ? cts.Token :
(_requestAborted == 1) ? new CancellationToken(true) :
RequestAbortedSource.Token;
}
set
{
// Set an abort token, overriding one we create internally. This setter and associated
// field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted.
_manuallySetRequestAbortToken = value;
}
}
private CancellationTokenSource RequestAbortedSource
{
get
{
// Get the abort token, lazily-initializing it if necessary.
// Make sure it's canceled if an abort request already came in.
// EnsureInitialized can return null since _abortedCts is reset to null
// after it's already been initialized to a non-null value.
// If EnsureInitialized does return null, this property was accessed between
// requests so it's safe to return an ephemeral CancellationTokenSource.
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource())
?? new CancellationTokenSource();
if (_requestAborted == 1)
{
cts.Cancel();
}
return cts;
}
}
void IHttpRequestLifetimeFeature.Abort()
{
Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication));
}
}
}

View File

@ -3,9 +3,9 @@
using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private async Task ReadBody()
{
Exception error = null;
try
{
while (true)
@ -112,18 +113,24 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
}
}
catch (ConnectionResetException ex)
{
ConnectionReset();
error = ex;
}
catch (Exception ex)
{
_bodyInputPipe.Writer.Complete(ex);
error = ex;
}
finally
{
_bodyInputPipe.Writer.Complete();
_bodyInputPipe.Writer.Complete(error);
}
}
private async Task WriteBody(bool flush = false)
{
Exception error = null;
try
{
while (true)
@ -160,17 +167,51 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
}
// We want to swallow IO exception and allow app to finish writing
catch (ConnectionResetException)
{
ConnectionReset();
}
catch (Exception ex)
{
if (!(ex is IOException))
{
_bodyOutput.Reader.Complete(ex);
}
error = ex;
}
finally
{
_bodyOutput.Reader.Complete();
_bodyOutput.Reader.Complete(error);
}
}
private void AbortIO()
{
if (Interlocked.CompareExchange(ref _requestAborted, 1, 0) != 0)
{
return;
}
_bodyOutput.Dispose();
try
{
_abortedCts?.Cancel();
}
catch (Exception)
{
// ignore
}
}
public void Abort(Exception reason)
{
_bodyOutput.Abort(reason);
_streams.Abort(reason);
NativeMethods.HttpCloseConnection(_pInProcessHandler);
AbortIO();
}
internal void ConnectionReset()
{
AbortIO();
}
}
}

View File

@ -15,6 +15,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.Server.IIS.Core.IO;
@ -32,6 +33,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private readonly IISServerOptions _options;
protected Streams _streams;
private volatile bool _hasResponseStarted;
private volatile bool _hasRequestReadingStarted;
@ -62,7 +65,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private const string BasicString = "Basic";
internal unsafe IISHttpContext(MemoryPool<byte> memoryPool, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server)
internal unsafe IISHttpContext(
MemoryPool<byte> memoryPool,
IntPtr pInProcessHandler,
IISServerOptions options,
IISHttpServer server)
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
{
_memoryPool = memoryPool;
@ -78,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
public string Path { get; set; }
public string QueryString { get; set; }
public string RawTarget { get; set; }
public CancellationToken RequestAborted { get; set; }
public bool HasResponseStarted => _hasResponseStarted;
public IPAddress RemoteIpAddress { get; set; }
public int RemotePort { get; set; }
@ -151,9 +158,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_currentIHttpUpgradeFeature = null;
}
RequestBody = new IISHttpRequestBody(this);
ResponseBody = new IISHttpResponseBody(this);
_streams = new Streams(this);
(RequestBody, ResponseBody) = _streams.Start();
var pipe = new Pipe(
new PipeOptions(
@ -223,9 +230,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
// Client might be disconnected at this point
// don't leak the exception
catch (IOException)
catch (ConnectionResetException)
{
// ignore
ConnectionReset();
}
}
@ -255,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private void ThrowResponseAbortedException()
{
throw new ObjectDisposedException("Unhandled application exception", _applicationException);
throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException);
}
protected Task ProduceEnd()
@ -340,11 +347,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
}
public void Abort()
{
// TODO
}
public abstract Task<bool> ProcessRequestAsync();
public void OnStarting(Func<object, Task> callback, object state)
@ -459,27 +461,28 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
_thisHandle.Free();
}
if (WindowsUser?.Identity is WindowsIdentity wi)
{
wi.Dispose();
}
_abortedCts?.Dispose();
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public override void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(disposing: true);
}
private void ThrowResponseAlreadyStartedException(string value)
private void ThrowResponseAlreadyStartedException(string name)
{
throw new InvalidOperationException("Response already started");
throw new InvalidOperationException(CoreStrings.FormatParameterReadOnlyAfterResponseStarted(name));
}
private WindowsPrincipal GetWindowsPrincipal()

View File

@ -44,6 +44,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
finally
{
_streams.Stop();
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
{
await FireOnStarting();
@ -91,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
// Cancel all remaining IO, there might be reads pending if not entire request body was sent by client
AsyncIO.Dispose();
AsyncIO?.Dispose();
if (_readBodyTask != null)
{

View File

@ -1,65 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class IISHttpResponseBody : Stream
{
private readonly IISHttpContext _httpContext;
public IISHttpResponseBody(IISHttpContext httpContext)
{
_httpContext = httpContext;
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public unsafe override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public override unsafe Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _httpContext.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _httpContext.FlushAsync(cancellationToken);
}
}
}

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private static readonly NativeMethods.PFN_REQUEST_HANDLER _requestHandler = HandleRequest;
private static readonly NativeMethods.PFN_SHUTDOWN_HANDLER _shutdownHandler = HandleShutdown;
private static readonly NativeMethods.PFN_DISCONNECT_HANDLER _onDisconnect = OnDisconnect;
private static readonly NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
private IISContextFactory _iisContextFactory;
@ -82,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_httpServerHandle = GCHandle.Alloc(this);
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this);
_nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
_nativeApplication.RegisterCallbacks(_requestHandler, _shutdownHandler, _onDisconnect, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
return Task.CompletedTask;
}
@ -199,6 +200,22 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
return true;
}
private static void OnDisconnect(IntPtr pvManagedHttpContext)
{
IISHttpContext context = null;
try
{
context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target;
context.ConnectionReset();
}
catch (Exception ex)
{
context?.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(OnDisconnect)}.");
}
}
private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes)
{
IISHttpContext context = null;

View File

@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
public void RegisterCallbacks(
NativeMethods.PFN_REQUEST_HANDLER requestHandler,
NativeMethods.PFN_SHUTDOWN_HANDLER shutdownHandler,
NativeMethods.PFN_DISCONNECT_HANDLER disconnectHandler,
NativeMethods.PFN_ASYNC_COMPLETION onAsyncCompletion,
IntPtr requestContext,
IntPtr shutdownContext)
@ -36,7 +37,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_nativeApplication,
requestHandler,
shutdownHandler,
onAsyncCompletion,
disconnectHandler,
onAsyncCompletion,
requestContext,
shutdownContext);
}

View File

@ -3,9 +3,11 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_completed = true;
_pipe.Reader.CancelPendingRead();
_pipe.Writer.Complete(error);
_pipe.Writer.Complete();
}
}
@ -77,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
if (_completed)
{
throw new ObjectDisposedException("Response is already completed");
return Task.CompletedTask;
}
_pipe.Writer.Write(buffer.Span);
@ -119,11 +121,14 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
await _flushTcs.Task;
cancellationToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
catch (OperationCanceledException ex)
{
_pipe.Writer.Complete();
_completed = true;
throw;
Abort(new ConnectionAbortedException(CoreStrings.ConnectionOrStreamAbortedByCancellationToken, ex));
}
catch
{
// A canceled token is the only reason flush should ever throw.
Debug.Assert(false);
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal abstract class ReadOnlyStream : Stream
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override int WriteTimeout
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override bool CanSeek => false;
public override long Length
=> throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class Streams
{
private static readonly ThrowingWasUpgradedWriteOnlyStream _throwingResponseStream
= new ThrowingWasUpgradedWriteOnlyStream();
private readonly IISHttpContext _context;
private readonly HttpResponseStream _response;
private readonly HttpRequestStream _request;
private readonly WrappingStream _upgradeableRequest;
private readonly WrappingStream _upgradeableResponse;
private EmptyStream _emptyRequest;
private Stream _upgradeStream;
public Streams(IISHttpContext context)
{
_context = context;
_request = new HttpRequestStream(_context);
_response = new HttpResponseStream(_context, _context);
_upgradeableResponse = new WrappingStream(_response);
_upgradeableRequest = new WrappingStream(_request);
}
public Stream Upgrade()
{
_upgradeStream = new HttpUpgradeStream(_request, _response);
// causes writes to context.Response.Body to throw
_upgradeableResponse.SetInnerStream(_throwingResponseStream);
_emptyRequest = new EmptyStream(_context);
_upgradeableRequest.SetInnerStream(_emptyRequest);
// _upgradeStream always uses _response
return _upgradeStream;
}
public (Stream request, Stream response) Start()
{
_request.StartAcceptingReads(_context);
_response.StartAcceptingWrites();
return (_upgradeableRequest, _upgradeableResponse);
}
public void Stop()
{
_request.StopAcceptingReads();
_emptyRequest?.StopAcceptingReads();
_response.StopAcceptingWrites();
}
public void Abort(Exception error)
{
_request.Abort(error);
_emptyRequest?.Abort(error);
_response.Abort();
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
public class ThrowingWasUpgradedWriteOnlyStream : WriteOnlyStream
{
public override void Write(byte[] buffer, int offset, int count)
=> throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded);
public override void Flush()
=> throw new InvalidOperationException(CoreStrings.ResponseStreamWasUpgraded);
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotSupportedException();
public override void SetLength(long value)
=> throw new NotSupportedException();
}
}

View File

@ -0,0 +1,144 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class WrappingStream : Stream
{
private Stream _inner;
private bool _disposed;
public WrappingStream(Stream inner)
{
_inner = inner;
}
public void SetInnerStream(Stream inner)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(WrappingStream));
}
_inner = inner;
}
public override bool CanRead => _inner.CanRead;
public override bool CanSeek => _inner.CanSeek;
public override bool CanWrite => _inner.CanWrite;
public override bool CanTimeout => _inner.CanTimeout;
public override long Length => _inner.Length;
public override long Position
{
get => _inner.Position;
set => _inner.Position = value;
}
public override int ReadTimeout
{
get => _inner.ReadTimeout;
set => _inner.ReadTimeout = value;
}
public override int WriteTimeout
{
get => _inner.WriteTimeout;
set => _inner.WriteTimeout = value;
}
public override void Flush()
=> _inner.Flush();
public override Task FlushAsync(CancellationToken cancellationToken)
=> _inner.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count)
=> _inner.Read(buffer, offset, count);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> _inner.ReadAsync(buffer, offset, count, cancellationToken);
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
=> _inner.ReadAsync(destination, cancellationToken);
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
public override int ReadByte()
=> _inner.ReadByte();
public override long Seek(long offset, SeekOrigin origin)
=> _inner.Seek(offset, origin);
public override void SetLength(long value)
=> _inner.SetLength(value);
public override void Write(byte[] buffer, int offset, int count)
=> _inner.Write(buffer, offset, count);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> _inner.WriteAsync(buffer, offset, count, cancellationToken);
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
=> _inner.WriteAsync(source, cancellationToken);
#elif NETSTANDARD2_0
#else
#error TFMs need to be updated
#endif
public override void WriteByte(byte value)
=> _inner.WriteByte(value);
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
=> _inner.CopyToAsync(destination, bufferSize, cancellationToken);
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> _inner.BeginRead(buffer, offset, count, callback, state);
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> _inner.BeginWrite(buffer, offset, count, callback, state);
public override int EndRead(IAsyncResult asyncResult)
=> _inner.EndRead(asyncResult);
public override void EndWrite(IAsyncResult asyncResult)
=> _inner.EndWrite(asyncResult);
public override object InitializeLifetimeService()
=> _inner.InitializeLifetimeService();
public override void Close()
=> _inner.Close();
public override bool Equals(object obj)
=> _inner.Equals(obj);
public override int GetHashCode()
=> _inner.GetHashCode();
public override string ToString()
=> _inner.ToString();
protected override void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
_inner.Dispose();
}
}
}
}

View File

@ -8,40 +8,36 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal class IISHttpRequestBody : Stream
public abstract class WriteOnlyStream : Stream
{
private readonly IISHttpContext _httpContext;
public override bool CanRead => false;
public IISHttpRequestBody(IISHttpContext httpContext)
public override bool CanWrite => true;
public override int ReadTimeout
{
_httpContext = httpContext;
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush()
public override long Position
{
throw new NotSupportedException();
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
throw new NotSupportedException();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var memory = new Memory<byte>(buffer, offset, count);
return _httpContext.ReadAsync(memory, cancellationToken);
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
@ -53,10 +49,5 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ResponseStreamWasUpgraded" xml:space="preserve">
<value>Cannot write to response body after connection has been upgraded.</value>
</data>
<data name="UnhandledApplicationException" xml:space="preserve">
<value>The response has been aborted due to an unhandled application exception.</value>
</data>
<data name="CannotUpgradeNonUpgradableRequest" xml:space="preserve">
<value>Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded.</value>
</data>
<data name="UpgradeCannotBeCalledMultipleTimes" xml:space="preserve">
<value>IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection.</value>
</data>
<data name="SynchronousReadsDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.</value>
</data>
<data name="SynchronousWritesDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.</value>
</data>
<data name="WritingToResponseBodyAfterResponseCompleted" xml:space="preserve">
<value>Cannot write to the response body, the response has completed.</value>
</data>
<data name="ConnectionAbortedByApplication" xml:space="preserve">
<value>The connection was aborted by the application.</value>
</data>
<data name="ConnectionOrStreamAbortedByCancellationToken" xml:space="preserve">
<value>The connection or stream was aborted because a write operation was aborted with a CancellationToken.</value>
</data>
<data name="ParameterReadOnlyAfterResponseStarted" xml:space="preserve">
<value>{name} cannot be set because the response has already started.</value>
</data>
</root>

View File

@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Server.IIS
}
public delegate REQUEST_NOTIFICATION_STATUS PFN_REQUEST_HANDLER(IntPtr pInProcessHandler, IntPtr pvRequestContext);
public delegate void PFN_DISCONNECT_HANDLER(IntPtr pvManagedHttpContext);
public delegate bool PFN_SHUTDOWN_HANDLER(IntPtr pvRequestContext);
public delegate REQUEST_NOTIFICATION_STATUS PFN_ASYNC_COMPLETION(IntPtr pvManagedHttpContext, int hr, int bytes);
public delegate REQUEST_NOTIFICATION_STATUS PFN_WEBSOCKET_ASYNC_COMPLETION(IntPtr pInProcessHandler, IntPtr completionInfo, IntPtr pvCompletionContext);
@ -56,6 +57,7 @@ namespace Microsoft.AspNetCore.Server.IIS
private static extern int register_callbacks(IntPtr pInProcessApplication,
PFN_REQUEST_HANDLER requestCallback,
PFN_SHUTDOWN_HANDLER shutdownCallback,
PFN_DISCONNECT_HANDLER disconnectCallback,
PFN_ASYNC_COMPLETION asyncCallback,
IntPtr pvRequestContext,
IntPtr pvShutdownContext);
@ -130,6 +132,9 @@ namespace Microsoft.AspNetCore.Server.IIS
[DllImport(AspNetCoreModuleDll)]
private static extern int http_cancel_io(IntPtr pInProcessHandler);
[DllImport(AspNetCoreModuleDll)]
private static extern int http_close_connection(IntPtr pInProcessHandler);
[DllImport(AspNetCoreModuleDll)]
private static extern unsafe int http_response_set_unknown_header(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace);
@ -152,11 +157,12 @@ namespace Microsoft.AspNetCore.Server.IIS
public static void HttpRegisterCallbacks(IntPtr pInProcessApplication,
PFN_REQUEST_HANDLER requestCallback,
PFN_SHUTDOWN_HANDLER shutdownCallback,
PFN_DISCONNECT_HANDLER disconnectCallback,
PFN_ASYNC_COMPLETION asyncCallback,
IntPtr pvRequestContext,
IntPtr pvShutdownContext)
{
Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, asyncCallback, pvRequestContext, pvShutdownContext));
Validate(register_callbacks(pInProcessApplication, requestCallback, shutdownCallback, disconnectCallback, asyncCallback, pvRequestContext, pvShutdownContext));
}
public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected)
@ -266,6 +272,11 @@ namespace Microsoft.AspNetCore.Server.IIS
return true;
}
public static void HttpCloseConnection(IntPtr pInProcessHandler)
{
Validate(http_close_connection(pInProcessHandler));
}
public static unsafe void HttpResponseSetUnknownHeader(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace)
{
Validate(http_response_set_unknown_header(pInProcessHandler, pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace));

View File

@ -0,0 +1,170 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Server.IIS
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class CoreStrings
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Server.IIS.CoreStrings", typeof(CoreStrings).GetTypeInfo().Assembly);
/// <summary>
/// Cannot write to response body after connection has been upgraded.
/// </summary>
internal static string ResponseStreamWasUpgraded
{
get => GetString("ResponseStreamWasUpgraded");
}
/// <summary>
/// Cannot write to response body after connection has been upgraded.
/// </summary>
internal static string FormatResponseStreamWasUpgraded()
=> GetString("ResponseStreamWasUpgraded");
/// <summary>
/// The response has been aborted due to an unhandled application exception.
/// </summary>
internal static string UnhandledApplicationException
{
get => GetString("UnhandledApplicationException");
}
/// <summary>
/// The response has been aborted due to an unhandled application exception.
/// </summary>
internal static string FormatUnhandledApplicationException()
=> GetString("UnhandledApplicationException");
/// <summary>
/// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded.
/// </summary>
internal static string CannotUpgradeNonUpgradableRequest
{
get => GetString("CannotUpgradeNonUpgradableRequest");
}
/// <summary>
/// Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded.
/// </summary>
internal static string FormatCannotUpgradeNonUpgradableRequest()
=> GetString("CannotUpgradeNonUpgradableRequest");
/// <summary>
/// IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection.
/// </summary>
internal static string UpgradeCannotBeCalledMultipleTimes
{
get => GetString("UpgradeCannotBeCalledMultipleTimes");
}
/// <summary>
/// IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection.
/// </summary>
internal static string FormatUpgradeCannotBeCalledMultipleTimes()
=> GetString("UpgradeCannotBeCalledMultipleTimes");
/// <summary>
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string SynchronousReadsDisallowed
{
get => GetString("SynchronousReadsDisallowed");
}
/// <summary>
/// Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string FormatSynchronousReadsDisallowed()
=> GetString("SynchronousReadsDisallowed");
/// <summary>
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string SynchronousWritesDisallowed
{
get => GetString("SynchronousWritesDisallowed");
}
/// <summary>
/// Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
/// </summary>
internal static string FormatSynchronousWritesDisallowed()
=> GetString("SynchronousWritesDisallowed");
/// <summary>
/// Cannot write to the response body, the response has completed.
/// </summary>
internal static string WritingToResponseBodyAfterResponseCompleted
{
get => GetString("WritingToResponseBodyAfterResponseCompleted");
}
/// <summary>
/// Cannot write to the response body, the response has completed.
/// </summary>
internal static string FormatWritingToResponseBodyAfterResponseCompleted()
=> GetString("WritingToResponseBodyAfterResponseCompleted");
/// <summary>
/// The connection was aborted by the application.
/// </summary>
internal static string ConnectionAbortedByApplication
{
get => GetString("ConnectionAbortedByApplication");
}
/// <summary>
/// The connection was aborted by the application.
/// </summary>
internal static string FormatConnectionAbortedByApplication()
=> GetString("ConnectionAbortedByApplication");
/// <summary>
/// The connection or stream was aborted because a write operation was aborted with a CancellationToken.
/// </summary>
internal static string ConnectionOrStreamAbortedByCancellationToken
{
get => GetString("ConnectionOrStreamAbortedByCancellationToken");
}
/// <summary>
/// The connection or stream was aborted because a write operation was aborted with a CancellationToken.
/// </summary>
internal static string FormatConnectionOrStreamAbortedByCancellationToken()
=> GetString("ConnectionOrStreamAbortedByCancellationToken");
/// <summary>
/// {name} cannot be set because the response has already started.
/// </summary>
internal static string ParameterReadOnlyAfterResponseStarted
{
get => GetString("ParameterReadOnlyAfterResponseStarted");
}
/// <summary>
/// {name} cannot be set because the response has already started.
/// </summary>
internal static string FormatParameterReadOnlyAfterResponseStarted(object name)
=> string.Format(CultureInfo.CurrentCulture, GetString("ParameterReadOnlyAfterResponseStarted", "name"), name);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
try
{
await runningTask.TimeoutAfterDefault();
await runningTask.DefaultTimeout();
// if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app
// try again

View File

@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public static void AssertWorkerProcessStop(this IISDeploymentResult deploymentResult)
{
var hostProcess = deploymentResult.HostProcess;
Assert.True(hostProcess.WaitForExit((int)TimeoutExtensions.DefaultTimeout.TotalMilliseconds));
Assert.True(hostProcess.WaitForExit((int)TimeoutExtensions.DefaultTimeoutValue.TotalMilliseconds));
if (deploymentResult.DeploymentParameters.ServerType == ServerType.IISExpress)
{

View File

@ -48,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
public Socket Socket => _socket;
public Stream Stream => _stream;
public void Dispose()
{

View File

@ -11,11 +11,16 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
public static class TimeoutExtensions
{
public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(300);
public static TimeSpan DefaultTimeoutValue = TimeSpan.FromSeconds(300);
public static Task TimeoutAfterDefault(this Task task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1)
public static Task DefaultTimeout(this Task task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1)
{
return task.TimeoutAfter(DefaultTimeout, filePath, lineNumber);
return task.TimeoutAfter(DefaultTimeoutValue, filePath, lineNumber);
}
public static Task<T> DefaultTimeout<T>(this Task<T> task, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = -1)
{
return task.TimeoutAfter(DefaultTimeoutValue, filePath, lineNumber);
}
}
}

View File

@ -15,15 +15,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ClientDisconnectTests : LoggedTest
public class ClientDisconnectTests : StrictTestServerTests
{
[ConditionalFact]
public async Task WritesSucceedAfterClientDisconnect()
{
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var clientDisconnectedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStartedCompletionSource = CreateTaskCompletionSource();
var clientDisconnectedCompletionSource = CreateTaskCompletionSource();
var requestCompletedCompletionSource = CreateTaskCompletionSource();
var data = new byte[1024];
using (var testServer = await TestServer.Create(
@ -42,19 +42,59 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
await requestStartedCompletionSource.Task.DefaultTimeout();
}
clientDisconnectedCompletionSource.SetResult(true);
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
await requestCompletedCompletionSource.Task.DefaultTimeout();
}
}
[ConditionalFact]
public async Task WritesCancelledWhenUsingAbortedToken()
{
var requestStartedCompletionSource = CreateTaskCompletionSource();
var requestCompletedCompletionSource = CreateTaskCompletionSource();
Exception exception = null;
var data = new byte[1];
using (var testServer = await TestServer.Create(async ctx =>
{
requestStartedCompletionSource.SetResult(true);
try
{
while (true)
{
await ctx.Response.Body.WriteAsync(data, ctx.RequestAborted);
}
}
catch (Exception e)
{
exception = e;
}
requestCompletedCompletionSource.SetResult(true);
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await requestStartedCompletionSource.Task.DefaultTimeout();
}
await requestCompletedCompletionSource.Task.DefaultTimeout();
Assert.IsType<OperationCanceledException>(exception);
}
}
[ConditionalFact]
public async Task ReadThrowsAfterClientDisconnect()
{
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStartedCompletionSource = CreateTaskCompletionSource();
var requestCompletedCompletionSource = CreateTaskCompletionSource();
Exception exception = null;
@ -77,10 +117,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
await requestStartedCompletionSource.Task.DefaultTimeout();
}
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
await requestCompletedCompletionSource.Task.DefaultTimeout();
}
Assert.IsType<ConnectionResetException>(exception);
@ -90,8 +130,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
public async Task WriterThrowsCancelledException()
{
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStartedCompletionSource = CreateTaskCompletionSource();
var requestCompletedCompletionSource = CreateTaskCompletionSource();
Exception exception = null;
var cancellationTokenSource = new CancellationTokenSource();
@ -104,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
while (true)
{
await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token);
await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token);
}
}
catch (Exception e)
@ -119,9 +159,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
await SendContentLength1Post(connection);
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
await requestStartedCompletionSource.Task.DefaultTimeout();
cancellationTokenSource.Cancel();
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
await requestCompletedCompletionSource.Task.DefaultTimeout();
}
Assert.IsType<OperationCanceledException>(exception);
@ -130,8 +170,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
public async Task ReaderThrowsCancelledException()
{
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestStartedCompletionSource = CreateTaskCompletionSource();
var requestCompletedCompletionSource = CreateTaskCompletionSource();
Exception exception = null;
var cancellationTokenSource = new CancellationTokenSource();
@ -155,9 +195,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
await requestStartedCompletionSource.Task.DefaultTimeout();
cancellationTokenSource.Cancel();
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
await requestCompletedCompletionSource.Task.DefaultTimeout();
}
Assert.IsType<OperationCanceledException>(exception);
}
@ -166,7 +206,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact]
public async Task ReaderThrowsResetExceptionOnInvalidBody()
{
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestCompletedCompletionSource = CreateTaskCompletionSource();
Exception exception = null;
@ -195,7 +235,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
"",
"ZZZ",
"");
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
await requestCompletedCompletionSource.Task.DefaultTimeout();
}
}
@ -203,6 +243,27 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal("The client has disconnected", exception.Message);
}
[ConditionalFact]
public async Task RequestAbortedIsTrippedWithoutIO()
{
var requestStarted = CreateTaskCompletionSource();
var requestAborted = CreateTaskCompletionSource();
using (var testServer = await TestServer.Create(
async ctx => {
ctx.RequestAborted.Register(() => requestAborted.SetResult(true));
requestStarted.SetResult(true);
await requestAborted.Task;
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await requestStarted.Task;
}
await requestAborted.Task;
}
}
private static async Task SendContentLength1Post(TestConnection connection)
{
await connection.Send(

View File

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class HttpBodyControlFeatureTests : StrictTestServerTests
{
[ConditionalFact]
public async Task ThrowsOnSyncReadOrWrite()
{
Exception writeException = null;
Exception readException = null;
using (var testServer = await TestServer.Create(
ctx => {
var bodyControl = ctx.Features.Get<IHttpBodyControlFeature>();
bodyControl.AllowSynchronousIO = false;
try
{
ctx.Response.Body.Write(new byte[10]);
}
catch (Exception ex)
{
writeException = ex;
}
try
{
ctx.Request.Body.Read(new byte[10]);
}
catch (Exception ex)
{
readException = ex;
}
return Task.CompletedTask;
}, LoggerFactory))
{
await testServer.HttpClient.GetStringAsync("/");
}
Assert.IsType<InvalidOperationException>(readException);
Assert.IsType<InvalidOperationException>(writeException);
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ConnectionIdFeatureTests : StrictTestServerTests
{
[ConditionalFact]
public async Task ProvidesConnectionId()
{
string connectionId = null;
using (var testServer = await TestServer.Create(ctx => {
var connectionIdFeature = ctx.Features.Get<IHttpConnectionFeature>();
connectionId = connectionIdFeature.ConnectionId;
return Task.CompletedTask;
}, LoggerFactory))
{
await testServer.HttpClient.GetStringAsync("/");
}
Assert.NotNull(connectionId);
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[SkipIfHostableWebCoreNotAvailable]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
public class ResponseAbortTests : StrictTestServerTests
{
[ConditionalFact]
public async Task ClosesWithoutSendingAnything()
{
using (var testServer = await TestServer.Create(
ctx => {
ctx.Abort();
return Task.CompletedTask;
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await connection.WaitForConnectionClose();
}
}
}
[ConditionalFact]
public async Task ClosesAfterDataSent()
{
var bodyReceived = CreateTaskCompletionSource();
using (var testServer = await TestServer.Create(
async ctx => {
await ctx.Response.WriteAsync("Abort");
await ctx.Response.Body.FlushAsync();
await bodyReceived.Task.DefaultTimeout();
ctx.Abort();
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await connection.Receive(
"HTTP/1.1 200 OK",
"");
await connection.ReceiveHeaders(
"Transfer-Encoding: chunked",
"Server: Microsoft-IIS/10.0");
await connection.ReceiveChunk("Abort");
bodyReceived.SetResult(true);
await connection.WaitForConnectionClose();
}
}
}
[ConditionalFact]
public async Task ReadsThrowAfterAbort()
{
Exception exception = null;
using (var testServer = await TestServer.Create(
async ctx => {
ctx.Abort();
try
{
var a = new byte[10];
await ctx.Request.Body.ReadAsync(a);
}
catch (Exception e)
{
exception = e;
}
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await connection.WaitForConnectionClose();
}
}
Assert.IsType<ConnectionAbortedException>(exception);
}
[ConditionalFact]
public async Task WritesNoopAfterAbort()
{
Exception exception = null;
using (var testServer = await TestServer.Create(
async ctx => {
ctx.Abort();
try
{
await ctx.Response.Body.WriteAsync(new byte[10]);
}
catch (Exception e)
{
exception = e;
}
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await connection.WaitForConnectionClose();
}
}
Assert.Null(exception);
}
[ConditionalFact]
public async Task RequestAbortedIsTrippedAfterAbort()
{
bool tokenAborted = false;
using (var testServer = await TestServer.Create(
ctx => {
ctx.Abort();
tokenAborted = ctx.RequestAborted.IsCancellationRequested;
return Task.CompletedTask;
}, LoggerFactory))
{
using (var connection = testServer.CreateConnection())
{
await SendContentLength1Post(connection);
await connection.WaitForConnectionClose();
}
}
Assert.True(tokenAborted);
}
private static async Task SendContentLength1Post(TestConnection connection)
{
await connection.Send(
"POST / HTTP/1.1",
"Content-Length: 1",
"Host: localhost",
"",
"");
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class StrictTestServerTests: LoggedTest
{
public override void Dispose()
{
base.Dispose();
Assert.DoesNotContain(TestSink.Writes, w => w.LogLevel > LogLevel.Information);
}
protected static TaskCompletionSource<bool> CreateTaskCompletionSource()
{
return new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
}
}
}

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\testsite.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
<InProcessTestSite>true</InProcessTestSite>
</PropertyGroup>
<Import Project="..\..\..\build\testsite.props" />
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj" />
</ItemGroup>