Add client disconnect and connection abort support (#1388)
This commit is contained in:
parent
ece5ad36e2
commit
b6c311c14d
|
|
@ -36,8 +36,8 @@
|
||||||
<IISExpressArguments>/config:"$(IISExpressAppHostConfig)" /systray:false</IISExpressArguments>
|
<IISExpressArguments>/config:"$(IISExpressAppHostConfig)" /systray:false</IISExpressArguments>
|
||||||
<IISArguments>-h "$(IISAppHostConfig)"</IISArguments>
|
<IISArguments>-h "$(IISAppHostConfig)"</IISArguments>
|
||||||
|
|
||||||
<AncmPath>$(NativePlatform)\aspnetcore.dll</AncmPath>
|
<AncmPath>$(AspNetCoreModuleV1ShimDll)</AncmPath>
|
||||||
<AncmV2Path>$(NativePlatform)\aspnetcorev2.dll</AncmV2Path>
|
<AncmV2Path>$(AspNetCoreModuleV2ShimDll)</AncmV2Path>
|
||||||
<AncmInProcessRHPath>aspnetcorev2_inprocess.dll</AncmInProcessRHPath>
|
<AncmInProcessRHPath>aspnetcorev2_inprocess.dll</AncmInProcessRHPath>
|
||||||
<DotNetPath>$(userprofile)\.dotnet\$(NativePlatform)\dotnet.exe</DotNetPath>
|
<DotNetPath>$(userprofile)\.dotnet\$(NativePlatform)\dotnet.exe</DotNetPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,7 @@
|
||||||
<ClInclude Include="applicationinfo.h" />
|
<ClInclude Include="applicationinfo.h" />
|
||||||
<ClInclude Include="AppOfflineApplication.h" />
|
<ClInclude Include="AppOfflineApplication.h" />
|
||||||
<ClInclude Include="AppOfflineHandler.h" />
|
<ClInclude Include="AppOfflineHandler.h" />
|
||||||
|
<ClInclude Include="DisconnectHandler.h" />
|
||||||
<ClInclude Include="ShimOptions.h" />
|
<ClInclude Include="ShimOptions.h" />
|
||||||
<ClInclude Include="globalmodule.h" />
|
<ClInclude Include="globalmodule.h" />
|
||||||
<ClInclude Include="applicationmanager.h" />
|
<ClInclude Include="applicationmanager.h" />
|
||||||
|
|
@ -246,6 +247,7 @@
|
||||||
<ClCompile Include="applicationmanager.cpp" />
|
<ClCompile Include="applicationmanager.cpp" />
|
||||||
<ClCompile Include="AppOfflineApplication.cpp" />
|
<ClCompile Include="AppOfflineApplication.cpp" />
|
||||||
<ClCompile Include="AppOfflineHandler.cpp" />
|
<ClCompile Include="AppOfflineHandler.cpp" />
|
||||||
|
<ClCompile Include="DisconnectHandler.cpp" />
|
||||||
<ClCompile Include="ShimOptions.cpp" />
|
<ClCompile Include="ShimOptions.cpp" />
|
||||||
<ClCompile Include="dllmain.cpp" />
|
<ClCompile Include="dllmain.cpp" />
|
||||||
<ClCompile Include="globalmodule.cpp" />
|
<ClCompile Include="globalmodule.cpp" />
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ BOOL WINAPI DllMain(HMODULE hModule,
|
||||||
switch (ul_reason_for_call)
|
switch (ul_reason_for_call)
|
||||||
{
|
{
|
||||||
case DLL_PROCESS_ATTACH:
|
case DLL_PROCESS_ATTACH:
|
||||||
|
|
||||||
ALLOC_CACHE_HANDLER::StaticInitialize();
|
ALLOC_CACHE_HANDLER::StaticInitialize();
|
||||||
g_hServerModule = hModule;
|
g_hServerModule = hModule;
|
||||||
DisableThreadLibraryCalls(hModule);
|
DisableThreadLibraryCalls(hModule);
|
||||||
|
|
@ -101,7 +101,7 @@ HRESULT
|
||||||
{
|
{
|
||||||
g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER);
|
g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether the feature is disabled due to security reason
|
// check whether the feature is disabled due to security reason
|
||||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
||||||
L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
|
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
|
// The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any
|
||||||
// static object initialized.
|
// static object initialized.
|
||||||
//
|
//
|
||||||
|
|
||||||
auto applicationManager = std::make_shared<APPLICATION_MANAGER>(g_hServerModule, *pHttpServer);
|
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(
|
RETURN_IF_FAILED(pModuleInfo->SetRequestNotifications(
|
||||||
moduleFactory.release(),
|
moduleFactory.release(),
|
||||||
|
|
@ -159,7 +159,7 @@ HRESULT
|
||||||
pGlobalModule.release(),
|
pGlobalModule.release(),
|
||||||
GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop
|
GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop
|
||||||
GL_STOP_LISTENING)); // worker process stop or recycle
|
GL_STOP_LISTENING)); // worker process stop or recycle
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
CATCH_RETURN()
|
CATCH_RETURN()
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,15 @@
|
||||||
#include "applicationmanager.h"
|
#include "applicationmanager.h"
|
||||||
#include "applicationinfo.h"
|
#include "applicationinfo.h"
|
||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
|
#include "DisconnectHandler.h"
|
||||||
|
|
||||||
extern BOOL g_fInShutdown;
|
extern BOOL g_fInShutdown;
|
||||||
|
|
||||||
__override
|
__override
|
||||||
|
|
||||||
ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(std::shared_ptr<APPLICATION_MANAGER> applicationManager) noexcept
|
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_pApplicationManager(std::move(applicationManager)),
|
||||||
|
m_moduleId(moduleId)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,10 +24,10 @@ ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule(
|
||||||
IModuleAllocator * pAllocator
|
IModuleAllocator * pAllocator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
#pragma warning( push )
|
#pragma warning( push )
|
||||||
#pragma warning ( disable : 26409 ) // Disable "Avoid using new"
|
#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 )
|
#pragma warning( push )
|
||||||
if (*ppModule == nullptr)
|
if (*ppModule == nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -58,13 +60,23 @@ Return value:
|
||||||
delete this;
|
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_pApplicationManager(std::move(applicationManager)),
|
||||||
m_pApplicationInfo(nullptr),
|
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
|
__override
|
||||||
REQUEST_NOTIFICATION_STATUS
|
REQUEST_NOTIFICATION_STATUS
|
||||||
ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
||||||
|
|
@ -77,12 +89,31 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
if (g_fInShutdown)
|
if (g_fInShutdown)
|
||||||
{
|
{
|
||||||
FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS));
|
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(
|
FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo(
|
||||||
*pHttpContext,
|
*pHttpContext,
|
||||||
m_pApplicationInfo));
|
m_pApplicationInfo));
|
||||||
|
|
@ -135,3 +166,8 @@ ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion(
|
||||||
return RQ_NOTIFICATION_FINISH_REQUEST;
|
return RQ_NOTIFICATION_FINISH_REQUEST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ASPNET_CORE_PROXY_MODULE::NotifyDisconnect() const
|
||||||
|
{
|
||||||
|
m_pHandler->NotifyDisconnect();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "applicationinfo.h"
|
#include "applicationinfo.h"
|
||||||
#include "irequesthandler.h"
|
#include "irequesthandler.h"
|
||||||
#include "applicationmanager.h"
|
#include "applicationmanager.h"
|
||||||
|
#include "DisconnectHandler.h"
|
||||||
|
|
||||||
extern HTTP_MODULE_ID g_pModuleId;
|
extern HTTP_MODULE_ID g_pModuleId;
|
||||||
|
|
||||||
|
|
@ -14,9 +15,9 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
|
||||||
{
|
{
|
||||||
public:
|
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)
|
void * operator new(size_t size, IModuleAllocator * pPlacement)
|
||||||
{
|
{
|
||||||
|
|
@ -46,16 +47,21 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
|
||||||
IHttpCompletionInfo * pCompletionInfo
|
IHttpCompletionInfo * pCompletionInfo
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
NotifyDisconnect() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
|
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
|
||||||
std::shared_ptr<APPLICATION_INFO> m_pApplicationInfo;
|
std::shared_ptr<APPLICATION_INFO> m_pApplicationInfo;
|
||||||
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> m_pHandler;
|
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
|
class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
|
||||||
{
|
{
|
||||||
public:
|
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;
|
virtual ~ASPNET_CORE_PROXY_MODULE_FACTORY() = default;
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
|
|
@ -66,7 +72,8 @@ class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
Terminate() noexcept override;
|
Terminate() noexcept override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
|
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;
|
||||||
|
HTTP_MODULE_ID m_moduleId;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ public:
|
||||||
|
|
||||||
virtual
|
virtual
|
||||||
VOID
|
VOID
|
||||||
TerminateRequest(
|
NotifyDisconnect() noexcept(false) = 0;
|
||||||
bool fClientInitiated
|
|
||||||
) = 0;
|
|
||||||
|
|
||||||
virtual
|
virtual
|
||||||
~IREQUEST_HANDLER(
|
~IREQUEST_HANDLER(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class REQUEST_HANDLER: public virtual IREQUEST_HANDLER
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VOID
|
VOID
|
||||||
ReferenceRequestHandler() noexcept override
|
ReferenceRequestHandler() noexcept override
|
||||||
{
|
{
|
||||||
InterlockedIncrement(&m_cRefs);
|
InterlockedIncrement(&m_cRefs);
|
||||||
}
|
}
|
||||||
|
|
@ -39,9 +39,11 @@ public:
|
||||||
return RQ_NOTIFICATION_FINISH_REQUEST;
|
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:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,14 @@ IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
|
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
|
||||||
{
|
{
|
||||||
StopClr();
|
StopClr();
|
||||||
InProcessApplicationBase::StopInternal(fServerInitiated);
|
InProcessApplicationBase::StopInternal(fServerInitiated);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
IN_PROCESS_APPLICATION::StopClr()
|
IN_PROCESS_APPLICATION::StopClr()
|
||||||
{
|
{
|
||||||
LOG_INFO(L"Stopping CLR");
|
LOG_INFO(L"Stopping CLR");
|
||||||
|
|
||||||
if (!m_blockManagedCallbacks)
|
if (!m_blockManagedCallbacks)
|
||||||
|
|
@ -86,6 +86,7 @@ VOID
|
||||||
IN_PROCESS_APPLICATION::SetCallbackHandles(
|
IN_PROCESS_APPLICATION::SetCallbackHandles(
|
||||||
_In_ PFN_REQUEST_HANDLER request_handler,
|
_In_ PFN_REQUEST_HANDLER request_handler,
|
||||||
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
|
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
|
||||||
|
_In_ PFN_DISCONNECT_HANDLER disconnect_callback,
|
||||||
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
|
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
|
||||||
_In_ VOID* pvRequstHandlerContext,
|
_In_ VOID* pvRequstHandlerContext,
|
||||||
_In_ VOID* pvShutdownHandlerContext
|
_In_ VOID* pvShutdownHandlerContext
|
||||||
|
|
@ -95,6 +96,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
|
||||||
|
|
||||||
m_RequestHandler = request_handler;
|
m_RequestHandler = request_handler;
|
||||||
m_RequestHandlerContext = pvRequstHandlerContext;
|
m_RequestHandlerContext = pvRequstHandlerContext;
|
||||||
|
m_DisconnectHandler = disconnect_callback;
|
||||||
m_ShutdownHandler = shutdown_handler;
|
m_ShutdownHandler = shutdown_handler;
|
||||||
m_ShutdownHandlerContext = pvShutdownHandlerContext;
|
m_ShutdownHandlerContext = pvShutdownHandlerContext;
|
||||||
m_AsyncCompletionHandler = async_completion_handler;
|
m_AsyncCompletionHandler = async_completion_handler;
|
||||||
|
|
@ -135,13 +137,13 @@ IN_PROCESS_APPLICATION::LoadManagedApplication()
|
||||||
}, ::ReferenceApplication(this));
|
}, ::ReferenceApplication(this));
|
||||||
|
|
||||||
LOG_INFO(L"Waiting for initialization");
|
LOG_INFO(L"Waiting for initialization");
|
||||||
|
|
||||||
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
|
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
|
||||||
|
|
||||||
// Wait for shutdown request
|
// Wait for shutdown request
|
||||||
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
|
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
|
||||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||||
|
|
||||||
if (waitResult == WAIT_TIMEOUT)
|
if (waitResult == WAIT_TIMEOUT)
|
||||||
{
|
{
|
||||||
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
|
// 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()
|
IN_PROCESS_APPLICATION::ExecuteApplication()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
|
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
|
||||||
|
|
||||||
auto context = std::make_shared<ExecuteClrContext>();
|
auto context = std::make_shared<ExecuteClrContext>();
|
||||||
|
|
||||||
auto pProc = s_fMainCallback;
|
auto pProc = s_fMainCallback;
|
||||||
|
|
@ -184,7 +186,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
||||||
// Get the entry point for main
|
// Get the entry point for main
|
||||||
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
|
pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
|
||||||
THROW_LAST_ERROR_IF_NULL(pProc);
|
THROW_LAST_ERROR_IF_NULL(pProc);
|
||||||
|
|
||||||
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
|
||||||
m_dotnetExeKnownLocation,
|
m_dotnetExeKnownLocation,
|
||||||
m_pConfig->QueryProcessPath(),
|
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
|
// We set a static so that managed code can call back into this instance and
|
||||||
// set the callbacks
|
// set the callbacks
|
||||||
s_Application = this;
|
s_Application = this;
|
||||||
|
|
||||||
//Start CLR thread
|
//Start CLR thread
|
||||||
m_clrThread = std::thread(ClrThreadEntryPoint, context);
|
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());
|
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
|
||||||
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
|
||||||
|
|
||||||
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
|
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
|
// in case when it was not initialized we need to keep server running to serve 502 page
|
||||||
if (m_Initialized)
|
if (m_Initialized)
|
||||||
{
|
{
|
||||||
QueueStop();
|
QueueStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +300,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
|
||||||
QueryApplicationId().c_str(),
|
QueryApplicationId().c_str(),
|
||||||
QueryApplicationPhysicalPath().c_str(),
|
QueryApplicationPhysicalPath().c_str(),
|
||||||
GetUnexpectedExceptionMessage(ex).c_str());
|
GetUnexpectedExceptionMessage(ex).c_str());
|
||||||
|
|
||||||
OBSERVE_CAUGHT_EXCEPTION();
|
OBSERVE_CAUGHT_EXCEPTION();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -500,9 +502,9 @@ IN_PROCESS_APPLICATION::CreateHandler(
|
||||||
{
|
{
|
||||||
try
|
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();
|
CATCH_RETURN();
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
class IN_PROCESS_HANDLER;
|
class IN_PROCESS_HANDLER;
|
||||||
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
|
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 BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext);
|
||||||
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion);
|
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion);
|
||||||
|
|
||||||
|
|
@ -33,6 +34,7 @@ public:
|
||||||
SetCallbackHandles(
|
SetCallbackHandles(
|
||||||
_In_ PFN_REQUEST_HANDLER request_callback,
|
_In_ PFN_REQUEST_HANDLER request_callback,
|
||||||
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
|
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
|
||||||
|
_In_ PFN_DISCONNECT_HANDLER disconnect_callback,
|
||||||
_In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback,
|
_In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback,
|
||||||
_In_ VOID* pvRequstHandlerContext,
|
_In_ VOID* pvRequstHandlerContext,
|
||||||
_In_ VOID* pvShutdownHandlerContext
|
_In_ VOID* pvShutdownHandlerContext
|
||||||
|
|
@ -48,14 +50,14 @@ public:
|
||||||
// Executes the .NET Core process
|
// Executes the .NET Core process
|
||||||
void
|
void
|
||||||
ExecuteApplication();
|
ExecuteApplication();
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
LoadManagedApplication();
|
LoadManagedApplication();
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
QueueStop();
|
QueueStop();
|
||||||
|
|
||||||
void
|
void
|
||||||
StopIncomingRequests()
|
StopIncomingRequests()
|
||||||
{
|
{
|
||||||
|
|
@ -110,7 +112,7 @@ public:
|
||||||
private:
|
private:
|
||||||
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
|
struct ExecuteClrContext: std::enable_shared_from_this<ExecuteClrContext>
|
||||||
{
|
{
|
||||||
ExecuteClrContext():
|
ExecuteClrContext():
|
||||||
m_argc(0),
|
m_argc(0),
|
||||||
m_pProc(nullptr),
|
m_pProc(nullptr),
|
||||||
m_exitCode(0),
|
m_exitCode(0),
|
||||||
|
|
@ -121,11 +123,11 @@ private:
|
||||||
DWORD m_argc;
|
DWORD m_argc;
|
||||||
std::unique_ptr<PCWSTR[]> m_argv;
|
std::unique_ptr<PCWSTR[]> m_argv;
|
||||||
hostfxr_main_fn m_pProc;
|
hostfxr_main_fn m_pProc;
|
||||||
|
|
||||||
int m_exitCode;
|
int m_exitCode;
|
||||||
int m_exceptionCode;
|
int m_exceptionCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Thread executing the .NET Core process this might be abandoned in timeout cases
|
// Thread executing the .NET Core process this might be abandoned in timeout cases
|
||||||
std::thread m_clrThread;
|
std::thread m_clrThread;
|
||||||
// Thread tracking the CLR thread, this one is always joined on shutdown
|
// Thread tracking the CLR thread, this one is always joined on shutdown
|
||||||
|
|
@ -144,6 +146,7 @@ private:
|
||||||
VOID* m_ShutdownHandlerContext;
|
VOID* m_ShutdownHandlerContext;
|
||||||
|
|
||||||
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
|
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
|
||||||
|
PFN_DISCONNECT_HANDLER m_DisconnectHandler;
|
||||||
|
|
||||||
std::wstring m_dotnetExeKnownLocation;
|
std::wstring m_dotnetExeKnownLocation;
|
||||||
|
|
||||||
|
|
@ -164,7 +167,7 @@ private:
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
SetEnvironmentVariablesOnWorkerProcess();
|
SetEnvironmentVariablesOnWorkerProcess();
|
||||||
|
|
||||||
void
|
void
|
||||||
StopClr();
|
StopClr();
|
||||||
|
|
||||||
|
|
@ -175,7 +178,7 @@ private:
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
ExecuteClr(const std::shared_ptr<ExecuteClrContext> &context);
|
ExecuteClr(const std::shared_ptr<ExecuteClrContext> &context);
|
||||||
|
|
||||||
// Allows to override call to hostfxr_main with custom callback
|
// Allows to override call to hostfxr_main with custom callback
|
||||||
// used in testing
|
// used in testing
|
||||||
inline static hostfxr_main_fn s_fMainCallback = nullptr;
|
inline static hostfxr_main_fn s_fMainCallback = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "aspnetcore_event.h"
|
#include "aspnetcore_event.h"
|
||||||
#include "IOutputManager.h"
|
#include "IOutputManager.h"
|
||||||
#include "ShuttingDownApplication.h"
|
#include "ShuttingDownApplication.h"
|
||||||
|
#include "ntassert.h"
|
||||||
|
|
||||||
ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL;
|
ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL;
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
|
||||||
_In_ IHttpContext *pW3Context,
|
_In_ IHttpContext *pW3Context,
|
||||||
_In_ PFN_REQUEST_HANDLER pRequestHandler,
|
_In_ PFN_REQUEST_HANDLER pRequestHandler,
|
||||||
_In_ void * pRequestHandlerContext,
|
_In_ void * pRequestHandlerContext,
|
||||||
|
_In_ PFN_DISCONNECT_HANDLER pDisconnectHandler,
|
||||||
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion
|
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion
|
||||||
): m_pManagedHttpContext(nullptr),
|
): m_pManagedHttpContext(nullptr),
|
||||||
m_requestNotificationStatus(RQ_NOTIFICATION_PENDING),
|
m_requestNotificationStatus(RQ_NOTIFICATION_PENDING),
|
||||||
|
|
@ -22,7 +24,8 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
|
||||||
m_pApplication(std::move(pApplication)),
|
m_pApplication(std::move(pApplication)),
|
||||||
m_pRequestHandler(pRequestHandler),
|
m_pRequestHandler(pRequestHandler),
|
||||||
m_pRequestHandlerContext(pRequestHandlerContext),
|
m_pRequestHandlerContext(pRequestHandlerContext),
|
||||||
m_pAsyncCompletionHandler(pAsyncCompletion)
|
m_pAsyncCompletionHandler(pAsyncCompletion),
|
||||||
|
m_pDisconnectHandler(pDisconnectHandler)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +66,7 @@ IN_PROCESS_HANDLER::OnExecuteRequestHandler()
|
||||||
{
|
{
|
||||||
return ServerShutdownMessage();
|
return ServerShutdownMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_pRequestHandler(this, m_pRequestHandlerContext);
|
return m_pRequestHandler(this, m_pRequestHandlerContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +90,7 @@ IN_PROCESS_HANDLER::OnAsyncCompletion(
|
||||||
return ServerShutdownMessage();
|
return ServerShutdownMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(m_pManagedHttpContext != nullptr);
|
||||||
// Call the managed handler for async completion.
|
// Call the managed handler for async completion.
|
||||||
return m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion);
|
return m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion);
|
||||||
}
|
}
|
||||||
|
|
@ -97,11 +101,16 @@ REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
IN_PROCESS_HANDLER::TerminateRequest(
|
IN_PROCESS_HANDLER::NotifyDisconnect()
|
||||||
bool fClientInitiated
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
UNREFERENCED_PARAMETER(fClientInitiated);
|
if (m_pApplication->QueryBlockCallbacksIntoManaged() ||
|
||||||
|
m_fManagedRequestComplete)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(m_pManagedHttpContext != nullptr);
|
||||||
|
m_pDisconnectHandler(m_pManagedHttpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
|
|
@ -110,6 +119,7 @@ IN_PROCESS_HANDLER::IndicateManagedRequestComplete(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
m_fManagedRequestComplete = TRUE;
|
m_fManagedRequestComplete = TRUE;
|
||||||
|
m_pManagedHttpContext = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ public:
|
||||||
_In_ IHttpContext *pW3Context,
|
_In_ IHttpContext *pW3Context,
|
||||||
_In_ PFN_REQUEST_HANDLER pRequestHandler,
|
_In_ PFN_REQUEST_HANDLER pRequestHandler,
|
||||||
_In_ void * pRequestHandlerContext,
|
_In_ void * pRequestHandlerContext,
|
||||||
|
_In_ PFN_DISCONNECT_HANDLER m_DisconnectHandler,
|
||||||
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion);
|
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion);
|
||||||
|
|
||||||
~IN_PROCESS_HANDLER() override = default;
|
~IN_PROCESS_HANDLER() override = default;
|
||||||
|
|
@ -35,10 +36,8 @@ public:
|
||||||
|
|
||||||
__override
|
__override
|
||||||
VOID
|
VOID
|
||||||
TerminateRequest(
|
NotifyDisconnect() override;
|
||||||
bool fClientInitiated
|
|
||||||
) override;
|
|
||||||
|
|
||||||
IHttpContext*
|
IHttpContext*
|
||||||
QueryHttpContext() const
|
QueryHttpContext() const
|
||||||
{
|
{
|
||||||
|
|
@ -82,6 +81,6 @@ private:
|
||||||
PFN_REQUEST_HANDLER m_pRequestHandler;
|
PFN_REQUEST_HANDLER m_pRequestHandler;
|
||||||
void* m_pRequestHandlerContext;
|
void* m_pRequestHandlerContext;
|
||||||
PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler;
|
PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler;
|
||||||
|
PFN_DISCONNECT_HANDLER m_pDisconnectHandler;
|
||||||
static ALLOC_CACHE_HANDLER * sm_pAlloc;
|
static ALLOC_CACHE_HANDLER * sm_pAlloc;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ register_callbacks(
|
||||||
_In_ IN_PROCESS_APPLICATION* pInProcessApplication,
|
_In_ IN_PROCESS_APPLICATION* pInProcessApplication,
|
||||||
_In_ PFN_REQUEST_HANDLER request_handler,
|
_In_ PFN_REQUEST_HANDLER request_handler,
|
||||||
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
|
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
|
||||||
|
_In_ PFN_DISCONNECT_HANDLER disconnect_handler,
|
||||||
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
|
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
|
||||||
_In_ VOID* pvRequstHandlerContext,
|
_In_ VOID* pvRequstHandlerContext,
|
||||||
_In_ VOID* pvShutdownHandlerContext
|
_In_ VOID* pvShutdownHandlerContext
|
||||||
|
|
@ -29,6 +30,7 @@ register_callbacks(
|
||||||
pInProcessApplication->SetCallbackHandles(
|
pInProcessApplication->SetCallbackHandles(
|
||||||
request_handler,
|
request_handler,
|
||||||
shutdown_handler,
|
shutdown_handler,
|
||||||
|
disconnect_handler,
|
||||||
async_completion_handler,
|
async_completion_handler,
|
||||||
pvRequstHandlerContext,
|
pvRequstHandlerContext,
|
||||||
pvShutdownHandlerContext
|
pvShutdownHandlerContext
|
||||||
|
|
@ -424,6 +426,16 @@ http_disable_buffering(
|
||||||
return S_OK;
|
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
|
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
|
||||||
HRESULT
|
HRESULT
|
||||||
http_response_set_unknown_header(
|
http_response_set_unknown_header(
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,6 @@
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="disconnectcontext.h" />
|
|
||||||
<ClInclude Include="environmentvariablehelpers.h" />
|
<ClInclude Include="environmentvariablehelpers.h" />
|
||||||
<ClInclude Include="forwarderconnection.h" />
|
<ClInclude Include="forwarderconnection.h" />
|
||||||
<ClInclude Include="processmanager.h" />
|
<ClInclude Include="processmanager.h" />
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
@ -46,7 +46,8 @@ FORWARDING_HANDLER::FORWARDING_HANDLER(
|
||||||
m_fServerResetConn(FALSE),
|
m_fServerResetConn(FALSE),
|
||||||
m_cRefs(1),
|
m_cRefs(1),
|
||||||
m_pW3Context(pW3Context),
|
m_pW3Context(pW3Context),
|
||||||
m_pApplication(std::move(pApplication))
|
m_pApplication(std::move(pApplication)),
|
||||||
|
m_fReactToDisconnect(FALSE)
|
||||||
{
|
{
|
||||||
LOG_TRACE(L"FORWARDING_HANDLER::FORWARDING_HANDLER");
|
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
|
// The m_pServer cleanup would happen afterwards, since there may be a
|
||||||
// call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders()
|
// call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders()
|
||||||
//
|
//
|
||||||
DBG_ASSERT(m_pDisconnect == NULL);
|
DBG_ASSERT(!m_fReactToDisconnect);
|
||||||
|
|
||||||
RemoveRequest();
|
RemoveRequest();
|
||||||
|
|
||||||
|
|
@ -93,7 +94,6 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
|
||||||
REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE;
|
REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE;
|
||||||
HRESULT hr = S_OK;
|
HRESULT hr = S_OK;
|
||||||
BOOL fRequestLocked = FALSE;
|
BOOL fRequestLocked = FALSE;
|
||||||
BOOL fHandleSet = FALSE;
|
|
||||||
BOOL fFailedToStartKestrel = FALSE;
|
BOOL fFailedToStartKestrel = FALSE;
|
||||||
BOOL fSecure = FALSE;
|
BOOL fSecure = FALSE;
|
||||||
HINTERNET hConnect = NULL;
|
HINTERNET hConnect = NULL;
|
||||||
|
|
@ -199,31 +199,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
|
||||||
goto Failure;
|
goto Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set client disconnect callback contract with IIS
|
m_fReactToDisconnect = TRUE;
|
||||||
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;
|
|
||||||
|
|
||||||
// require lock as client disconnect callback may happen
|
// require lock as client disconnect callback may happen
|
||||||
AcquireSRWLockShared(&m_RequestLock);
|
AcquireSRWLockShared(&m_RequestLock);
|
||||||
|
|
@ -2705,21 +2681,16 @@ FORWARDING_HANDLER::RemoveRequest(
|
||||||
VOID
|
VOID
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ASYNC_DISCONNECT_CONTEXT * pDisconnect;
|
m_fReactToDisconnect = FALSE;
|
||||||
pDisconnect = (ASYNC_DISCONNECT_CONTEXT *)InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL);
|
|
||||||
if (pDisconnect != NULL)
|
|
||||||
{
|
|
||||||
pDisconnect->ResetHandler();
|
|
||||||
pDisconnect = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
FORWARDING_HANDLER::TerminateRequest(
|
FORWARDING_HANDLER::NotifyDisconnect()
|
||||||
bool fClientInitiated
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
UNREFERENCED_PARAMETER(fClientInitiated);
|
if (!m_fReactToDisconnect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL fLocked = FALSE;
|
BOOL fLocked = FALSE;
|
||||||
if (TlsGetValue(g_dwTlsIndex) != this)
|
if (TlsGetValue(g_dwTlsIndex) != this)
|
||||||
|
|
@ -2740,7 +2711,7 @@ FORWARDING_HANDLER::TerminateRequest(
|
||||||
|
|
||||||
if (!m_fHttpHandleInClose)
|
if (!m_fHttpHandleInClose)
|
||||||
{
|
{
|
||||||
m_fClientDisconnected = fClientInitiated;
|
m_fClientDisconnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fLocked)
|
if (fLocked)
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,7 @@ public:
|
||||||
StaticTerminate();
|
StaticTerminate();
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
TerminateRequest(
|
NotifyDisconnect() override;
|
||||||
bool fClientInitiated
|
|
||||||
);
|
|
||||||
|
|
||||||
static void * operator new(size_t size);
|
static void * operator new(size_t size);
|
||||||
|
|
||||||
|
|
@ -220,7 +218,6 @@ private:
|
||||||
DWORD m_cMinBufferLimit;
|
DWORD m_cMinBufferLimit;
|
||||||
ULONGLONG m_cContentLength;
|
ULONGLONG m_cContentLength;
|
||||||
WEBSOCKET_HANDLER * m_pWebSocket;
|
WEBSOCKET_HANDLER * m_pWebSocket;
|
||||||
ASYNC_DISCONNECT_CONTEXT * m_pDisconnect;
|
|
||||||
|
|
||||||
BYTE * m_pEntityBuffer;
|
BYTE * m_pEntityBuffer;
|
||||||
static const SIZE_T INLINE_ENTITY_BUFFERS = 8;
|
static const SIZE_T INLINE_ENTITY_BUFFERS = 8;
|
||||||
|
|
@ -239,5 +236,5 @@ private:
|
||||||
mutable LONG m_cRefs;
|
mutable LONG m_cRefs;
|
||||||
IHttpContext* m_pW3Context;
|
IHttpContext* m_pW3Context;
|
||||||
std::unique_ptr<OUT_OF_PROCESS_APPLICATION, IAPPLICATION_DELETER> m_pApplication;
|
std::unique_ptr<OUT_OF_PROCESS_APPLICATION, IAPPLICATION_DELETER> m_pApplication;
|
||||||
HTTP_MODULE_ID m_pModuleId;
|
bool m_fReactToDisconnect;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include "aspnetcore_event.h"
|
#include "aspnetcore_event.h"
|
||||||
#include "aspnetcore_msg.h"
|
#include "aspnetcore_msg.h"
|
||||||
#include "disconnectcontext.h"
|
|
||||||
#include "requesthandler_config.h"
|
#include "requesthandler_config.h"
|
||||||
|
|
||||||
#include "sttimer.h"
|
#include "sttimer.h"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.Core
|
namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
// TODO redudant file, remove
|
|
||||||
// See https://github.com/aspnet/IISIntegration/issues/426
|
// See https://github.com/aspnet/IISIntegration/issues/426
|
||||||
internal class DuplexStream : Stream
|
internal class DuplexStream : Stream
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
IHttpAuthenticationFeature,
|
IHttpAuthenticationFeature,
|
||||||
IServerVariablesFeature,
|
IServerVariablesFeature,
|
||||||
IHttpBufferingFeature,
|
IHttpBufferingFeature,
|
||||||
ITlsConnectionFeature
|
ITlsConnectionFeature,
|
||||||
|
IHttpBodyControlFeature
|
||||||
{
|
{
|
||||||
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
|
// 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.
|
// 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 X509Certificate2 _certificate;
|
||||||
|
|
||||||
private List<KeyValuePair<Type, object>> MaybeExtra;
|
private List<KeyValuePair<Type, object>> MaybeExtra;
|
||||||
|
|
||||||
public void ResetFeatureCollection()
|
public void ResetFeatureCollection()
|
||||||
{
|
{
|
||||||
Initialize();
|
Initialize();
|
||||||
|
|
@ -179,12 +181,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
set => ResponseBody = value;
|
set => ResponseBody = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
|
|
||||||
{
|
|
||||||
get => RequestAborted;
|
|
||||||
set => RequestAborted = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
|
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
|
||||||
|
|
||||||
bool IHttpUpgradeFeature.IsUpgradableRequest => true;
|
bool IHttpUpgradeFeature.IsUpgradableRequest => true;
|
||||||
|
|
@ -259,19 +255,18 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
||||||
{
|
{
|
||||||
// TODO fix these exceptions strings
|
|
||||||
if (!((IHttpUpgradeFeature)this).IsUpgradableRequest)
|
if (!((IHttpUpgradeFeature)this).IsUpgradableRequest)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("CoreStrings.CannotUpgradeNonUpgradableRequest");
|
throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wasUpgraded)
|
if (_wasUpgraded)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
|
throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes);
|
||||||
}
|
}
|
||||||
if (HasResponseStarted)
|
if (HasResponseStarted)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
|
throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes);
|
||||||
}
|
}
|
||||||
|
|
||||||
_wasUpgraded = true;
|
_wasUpgraded = true;
|
||||||
|
|
@ -291,7 +286,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
await InitializeResponse(flushHeaders: true);
|
await InitializeResponse(flushHeaders: true);
|
||||||
|
|
||||||
return new DuplexStream(RequestBody, ResponseBody);
|
return _streams.Upgrade();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
|
Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
|
||||||
|
|
@ -327,10 +322,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
|
||||||
|
|
||||||
void IHttpRequestLifetimeFeature.Abort()
|
bool IHttpBodyControlFeature.AllowSynchronousIO { get; set; } = true;
|
||||||
{
|
|
||||||
Abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IHttpBufferingFeature.DisableRequestBuffering()
|
void IHttpBufferingFeature.DisableRequestBuffering()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.Core
|
namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
private async Task ReadBody()
|
private async Task ReadBody()
|
||||||
{
|
{
|
||||||
|
Exception error = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
|
@ -112,18 +113,24 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ConnectionResetException ex)
|
||||||
|
{
|
||||||
|
ConnectionReset();
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_bodyInputPipe.Writer.Complete(ex);
|
error = ex;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_bodyInputPipe.Writer.Complete();
|
_bodyInputPipe.Writer.Complete(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WriteBody(bool flush = false)
|
private async Task WriteBody(bool flush = false)
|
||||||
{
|
{
|
||||||
|
Exception error = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
|
@ -160,17 +167,51 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We want to swallow IO exception and allow app to finish writing
|
// We want to swallow IO exception and allow app to finish writing
|
||||||
|
catch (ConnectionResetException)
|
||||||
|
{
|
||||||
|
ConnectionReset();
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (!(ex is IOException))
|
error = ex;
|
||||||
{
|
|
||||||
_bodyOutput.Reader.Complete(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
using Microsoft.AspNetCore.HttpSys.Internal;
|
||||||
using Microsoft.AspNetCore.Server.IIS.Core.IO;
|
using Microsoft.AspNetCore.Server.IIS.Core.IO;
|
||||||
|
|
@ -32,6 +33,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
private readonly IISServerOptions _options;
|
private readonly IISServerOptions _options;
|
||||||
|
|
||||||
|
protected Streams _streams;
|
||||||
|
|
||||||
private volatile bool _hasResponseStarted;
|
private volatile bool _hasResponseStarted;
|
||||||
private volatile bool _hasRequestReadingStarted;
|
private volatile bool _hasRequestReadingStarted;
|
||||||
|
|
||||||
|
|
@ -62,7 +65,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
private const string BasicString = "Basic";
|
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))
|
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
|
||||||
{
|
{
|
||||||
_memoryPool = memoryPool;
|
_memoryPool = memoryPool;
|
||||||
|
|
@ -78,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string QueryString { get; set; }
|
public string QueryString { get; set; }
|
||||||
public string RawTarget { get; set; }
|
public string RawTarget { get; set; }
|
||||||
public CancellationToken RequestAborted { get; set; }
|
|
||||||
public bool HasResponseStarted => _hasResponseStarted;
|
public bool HasResponseStarted => _hasResponseStarted;
|
||||||
public IPAddress RemoteIpAddress { get; set; }
|
public IPAddress RemoteIpAddress { get; set; }
|
||||||
public int RemotePort { get; set; }
|
public int RemotePort { get; set; }
|
||||||
|
|
@ -151,9 +158,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
_currentIHttpUpgradeFeature = null;
|
_currentIHttpUpgradeFeature = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBody = new IISHttpRequestBody(this);
|
_streams = new Streams(this);
|
||||||
ResponseBody = new IISHttpResponseBody(this);
|
|
||||||
|
|
||||||
|
(RequestBody, ResponseBody) = _streams.Start();
|
||||||
|
|
||||||
var pipe = new Pipe(
|
var pipe = new Pipe(
|
||||||
new PipeOptions(
|
new PipeOptions(
|
||||||
|
|
@ -223,9 +230,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
}
|
}
|
||||||
// Client might be disconnected at this point
|
// Client might be disconnected at this point
|
||||||
// don't leak the exception
|
// don't leak the exception
|
||||||
catch (IOException)
|
catch (ConnectionResetException)
|
||||||
{
|
{
|
||||||
// ignore
|
ConnectionReset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
private void ThrowResponseAbortedException()
|
private void ThrowResponseAbortedException()
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException("Unhandled application exception", _applicationException);
|
throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task ProduceEnd()
|
protected Task ProduceEnd()
|
||||||
|
|
@ -340,11 +347,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Abort()
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task<bool> ProcessRequestAsync();
|
public abstract Task<bool> ProcessRequestAsync();
|
||||||
|
|
||||||
public void OnStarting(Func<object, Task> callback, object state)
|
public void OnStarting(Func<object, Task> callback, object state)
|
||||||
|
|
@ -459,27 +461,28 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
// TODO: dispose managed state (managed objects).
|
|
||||||
_thisHandle.Free();
|
_thisHandle.Free();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WindowsUser?.Identity is WindowsIdentity wi)
|
if (WindowsUser?.Identity is WindowsIdentity wi)
|
||||||
{
|
{
|
||||||
wi.Dispose();
|
wi.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_abortedCts?.Dispose();
|
||||||
|
|
||||||
disposedValue = true;
|
disposedValue = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This code added to correctly implement the disposable pattern.
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|
||||||
Dispose(disposing: true);
|
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()
|
private WindowsPrincipal GetWindowsPrincipal()
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_streams.Stop();
|
||||||
|
|
||||||
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
||||||
{
|
{
|
||||||
await FireOnStarting();
|
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
|
// 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)
|
if (_readBodyTask != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
|
|
||||||
private static readonly NativeMethods.PFN_REQUEST_HANDLER _requestHandler = HandleRequest;
|
private static readonly NativeMethods.PFN_REQUEST_HANDLER _requestHandler = HandleRequest;
|
||||||
private static readonly NativeMethods.PFN_SHUTDOWN_HANDLER _shutdownHandler = HandleShutdown;
|
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 static readonly NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
|
||||||
|
|
||||||
private IISContextFactory _iisContextFactory;
|
private IISContextFactory _iisContextFactory;
|
||||||
|
|
@ -82,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
_httpServerHandle = GCHandle.Alloc(this);
|
_httpServerHandle = GCHandle.Alloc(this);
|
||||||
|
|
||||||
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, 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;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +200,22 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
return true;
|
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)
|
private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes)
|
||||||
{
|
{
|
||||||
IISHttpContext context = null;
|
IISHttpContext context = null;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
public void RegisterCallbacks(
|
public void RegisterCallbacks(
|
||||||
NativeMethods.PFN_REQUEST_HANDLER requestHandler,
|
NativeMethods.PFN_REQUEST_HANDLER requestHandler,
|
||||||
NativeMethods.PFN_SHUTDOWN_HANDLER shutdownHandler,
|
NativeMethods.PFN_SHUTDOWN_HANDLER shutdownHandler,
|
||||||
|
NativeMethods.PFN_DISCONNECT_HANDLER disconnectHandler,
|
||||||
NativeMethods.PFN_ASYNC_COMPLETION onAsyncCompletion,
|
NativeMethods.PFN_ASYNC_COMPLETION onAsyncCompletion,
|
||||||
IntPtr requestContext,
|
IntPtr requestContext,
|
||||||
IntPtr shutdownContext)
|
IntPtr shutdownContext)
|
||||||
|
|
@ -36,7 +37,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
_nativeApplication,
|
_nativeApplication,
|
||||||
requestHandler,
|
requestHandler,
|
||||||
shutdownHandler,
|
shutdownHandler,
|
||||||
onAsyncCompletion,
|
disconnectHandler,
|
||||||
|
onAsyncCompletion,
|
||||||
requestContext,
|
requestContext,
|
||||||
shutdownContext);
|
shutdownContext);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.Core
|
namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
|
|
@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
_completed = true;
|
_completed = true;
|
||||||
|
|
||||||
_pipe.Reader.CancelPendingRead();
|
_pipe.Reader.CancelPendingRead();
|
||||||
_pipe.Writer.Complete(error);
|
_pipe.Writer.Complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
if (_completed)
|
if (_completed)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException("Response is already completed");
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pipe.Writer.Write(buffer.Span);
|
_pipe.Writer.Write(buffer.Span);
|
||||||
|
|
@ -119,11 +121,14 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
await _flushTcs.Task;
|
await _flushTcs.Task;
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
_pipe.Writer.Complete();
|
Abort(new ConnectionAbortedException(CoreStrings.ConnectionOrStreamAbortedByCancellationToken, ex));
|
||||||
_completed = true;
|
}
|
||||||
throw;
|
catch
|
||||||
|
{
|
||||||
|
// A canceled token is the only reason flush should ever throw.
|
||||||
|
Debug.Assert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,40 +8,36 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.Core
|
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 CanSeek => false;
|
||||||
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
|
|
||||||
public override long Length => throw new NotSupportedException();
|
public override long Length => throw new NotSupportedException();
|
||||||
|
|
||||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
public override long Position
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
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)
|
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var memory = new Memory<byte>(buffer, offset, count);
|
throw new NotSupportedException();
|
||||||
|
|
||||||
return _httpContext.ReadAsync(memory, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
|
@ -53,10 +49,5 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Server.IIS
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate REQUEST_NOTIFICATION_STATUS PFN_REQUEST_HANDLER(IntPtr pInProcessHandler, IntPtr pvRequestContext);
|
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 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_ASYNC_COMPLETION(IntPtr pvManagedHttpContext, int hr, int bytes);
|
||||||
public delegate REQUEST_NOTIFICATION_STATUS PFN_WEBSOCKET_ASYNC_COMPLETION(IntPtr pInProcessHandler, IntPtr completionInfo, IntPtr pvCompletionContext);
|
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,
|
private static extern int register_callbacks(IntPtr pInProcessApplication,
|
||||||
PFN_REQUEST_HANDLER requestCallback,
|
PFN_REQUEST_HANDLER requestCallback,
|
||||||
PFN_SHUTDOWN_HANDLER shutdownCallback,
|
PFN_SHUTDOWN_HANDLER shutdownCallback,
|
||||||
|
PFN_DISCONNECT_HANDLER disconnectCallback,
|
||||||
PFN_ASYNC_COMPLETION asyncCallback,
|
PFN_ASYNC_COMPLETION asyncCallback,
|
||||||
IntPtr pvRequestContext,
|
IntPtr pvRequestContext,
|
||||||
IntPtr pvShutdownContext);
|
IntPtr pvShutdownContext);
|
||||||
|
|
@ -130,6 +132,9 @@ namespace Microsoft.AspNetCore.Server.IIS
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
private static extern int http_cancel_io(IntPtr pInProcessHandler);
|
private static extern int http_cancel_io(IntPtr pInProcessHandler);
|
||||||
|
|
||||||
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
|
private static extern int http_close_connection(IntPtr pInProcessHandler);
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
private static extern unsafe int http_response_set_unknown_header(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace);
|
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,
|
public static void HttpRegisterCallbacks(IntPtr pInProcessApplication,
|
||||||
PFN_REQUEST_HANDLER requestCallback,
|
PFN_REQUEST_HANDLER requestCallback,
|
||||||
PFN_SHUTDOWN_HANDLER shutdownCallback,
|
PFN_SHUTDOWN_HANDLER shutdownCallback,
|
||||||
|
PFN_DISCONNECT_HANDLER disconnectCallback,
|
||||||
PFN_ASYNC_COMPLETION asyncCallback,
|
PFN_ASYNC_COMPLETION asyncCallback,
|
||||||
IntPtr pvRequestContext,
|
IntPtr pvRequestContext,
|
||||||
IntPtr pvShutdownContext)
|
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)
|
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;
|
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)
|
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));
|
Validate(http_response_set_unknown_header(pInProcessHandler, pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await runningTask.TimeoutAfterDefault();
|
await runningTask.DefaultTimeout();
|
||||||
|
|
||||||
// if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app
|
// if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app
|
||||||
// try again
|
// try again
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
public static void AssertWorkerProcessStop(this IISDeploymentResult deploymentResult)
|
public static void AssertWorkerProcessStop(this IISDeploymentResult deploymentResult)
|
||||||
{
|
{
|
||||||
var hostProcess = deploymentResult.HostProcess;
|
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)
|
if (deploymentResult.DeploymentParameters.ServerType == ServerType.IISExpress)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
}
|
}
|
||||||
|
|
||||||
public Socket Socket => _socket;
|
public Socket Socket => _socket;
|
||||||
|
public Stream Stream => _stream;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,16 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
|
||||||
public static class TimeoutExtensions
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
[SkipIfHostableWebCoreNotAvailable]
|
[SkipIfHostableWebCoreNotAvailable]
|
||||||
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
|
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, "https://github.com/aspnet/IISIntegration/issues/866")]
|
||||||
public class ClientDisconnectTests : LoggedTest
|
public class ClientDisconnectTests : StrictTestServerTests
|
||||||
{
|
{
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
public async Task WritesSucceedAfterClientDisconnect()
|
public async Task WritesSucceedAfterClientDisconnect()
|
||||||
{
|
{
|
||||||
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestStartedCompletionSource = CreateTaskCompletionSource();
|
||||||
var clientDisconnectedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var clientDisconnectedCompletionSource = CreateTaskCompletionSource();
|
||||||
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestCompletedCompletionSource = CreateTaskCompletionSource();
|
||||||
|
|
||||||
var data = new byte[1024];
|
var data = new byte[1024];
|
||||||
using (var testServer = await TestServer.Create(
|
using (var testServer = await TestServer.Create(
|
||||||
|
|
@ -42,19 +42,59 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
using (var connection = testServer.CreateConnection())
|
using (var connection = testServer.CreateConnection())
|
||||||
{
|
{
|
||||||
await SendContentLength1Post(connection);
|
await SendContentLength1Post(connection);
|
||||||
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
|
await requestStartedCompletionSource.Task.DefaultTimeout();
|
||||||
}
|
}
|
||||||
clientDisconnectedCompletionSource.SetResult(true);
|
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]
|
[ConditionalFact]
|
||||||
public async Task ReadThrowsAfterClientDisconnect()
|
public async Task ReadThrowsAfterClientDisconnect()
|
||||||
{
|
{
|
||||||
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestStartedCompletionSource = CreateTaskCompletionSource();
|
||||||
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestCompletedCompletionSource = CreateTaskCompletionSource();
|
||||||
|
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
|
|
||||||
|
|
@ -77,10 +117,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
using (var connection = testServer.CreateConnection())
|
using (var connection = testServer.CreateConnection())
|
||||||
{
|
{
|
||||||
await SendContentLength1Post(connection);
|
await SendContentLength1Post(connection);
|
||||||
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
|
await requestStartedCompletionSource.Task.DefaultTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
|
await requestCompletedCompletionSource.Task.DefaultTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsType<ConnectionResetException>(exception);
|
Assert.IsType<ConnectionResetException>(exception);
|
||||||
|
|
@ -90,8 +130,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
|
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
|
||||||
public async Task WriterThrowsCancelledException()
|
public async Task WriterThrowsCancelledException()
|
||||||
{
|
{
|
||||||
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestStartedCompletionSource = CreateTaskCompletionSource();
|
||||||
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestCompletedCompletionSource = CreateTaskCompletionSource();
|
||||||
|
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
@ -104,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token);
|
await ctx.Response.Body.WriteAsync(data, cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -119,9 +159,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
await SendContentLength1Post(connection);
|
await SendContentLength1Post(connection);
|
||||||
|
|
||||||
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
|
await requestStartedCompletionSource.Task.DefaultTimeout();
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
|
await requestCompletedCompletionSource.Task.DefaultTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsType<OperationCanceledException>(exception);
|
Assert.IsType<OperationCanceledException>(exception);
|
||||||
|
|
@ -130,8 +170,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
|
[ConditionalFact(Skip = "See: https://github.com/aspnet/IISIntegration/issues/1075")]
|
||||||
public async Task ReaderThrowsCancelledException()
|
public async Task ReaderThrowsCancelledException()
|
||||||
{
|
{
|
||||||
var requestStartedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestStartedCompletionSource = CreateTaskCompletionSource();
|
||||||
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestCompletedCompletionSource = CreateTaskCompletionSource();
|
||||||
|
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
@ -155,9 +195,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
using (var connection = testServer.CreateConnection())
|
using (var connection = testServer.CreateConnection())
|
||||||
{
|
{
|
||||||
await SendContentLength1Post(connection);
|
await SendContentLength1Post(connection);
|
||||||
await requestStartedCompletionSource.Task.TimeoutAfterDefault();
|
await requestStartedCompletionSource.Task.DefaultTimeout();
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
await requestCompletedCompletionSource.Task.TimeoutAfterDefault();
|
await requestCompletedCompletionSource.Task.DefaultTimeout();
|
||||||
}
|
}
|
||||||
Assert.IsType<OperationCanceledException>(exception);
|
Assert.IsType<OperationCanceledException>(exception);
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +206,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
public async Task ReaderThrowsResetExceptionOnInvalidBody()
|
public async Task ReaderThrowsResetExceptionOnInvalidBody()
|
||||||
{
|
{
|
||||||
var requestCompletedCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var requestCompletedCompletionSource = CreateTaskCompletionSource();
|
||||||
|
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
|
|
||||||
|
|
@ -195,7 +235,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
"",
|
"",
|
||||||
"ZZZ",
|
"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);
|
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)
|
private static async Task SendContentLength1Post(TestConnection connection)
|
||||||
{
|
{
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<Import Project="..\..\..\build\testsite.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||||
<InProcessTestSite>true</InProcessTestSite>
|
<InProcessTestSite>true</InProcessTestSite>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Import Project="..\..\..\build\testsite.props" />
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj" />
|
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Server.IIS\Microsoft.AspNetCore.Server.IIS.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue