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