1129 lines
24 KiB
C++
1129 lines
24 KiB
C++
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the MIT License. See License.txt in the project root for license information.
|
|
|
|
/*++
|
|
|
|
Abstract:
|
|
|
|
Main Handler for websocket requests.
|
|
|
|
Initiates websocket connection to backend.
|
|
Uses WinHttp API's for backend connections,
|
|
and IIS Websocket API's for sending/receiving
|
|
websocket traffic.
|
|
|
|
Transfers data between the two IO endpoints.
|
|
|
|
-----------------
|
|
Read Loop Design
|
|
-----------------
|
|
When a read IO completes successfully on any endpoints, Asp.Net Core Module doesn't
|
|
immediately issue the next read. The next read is initiated only after
|
|
the read data is sent to the other endpoint. As soon as this send completes,
|
|
we initiate the next IO. It should be noted that the send complete merely
|
|
indicates the API completion from HTTP, and not necessarily over the network.
|
|
|
|
This prevents the need for data buffering at the Asp.Net Core Module level.
|
|
|
|
--*/
|
|
|
|
#include "websockethandler.h"
|
|
#include "exceptions.h"
|
|
|
|
SRWLOCK WEBSOCKET_HANDLER::sm_RequestsListLock;
|
|
|
|
LIST_ENTRY WEBSOCKET_HANDLER::sm_RequestsListHead;
|
|
|
|
TRACE_LOG * WEBSOCKET_HANDLER::sm_pTraceLog;
|
|
|
|
WEBSOCKET_HANDLER::WEBSOCKET_HANDLER() :
|
|
_pHttpContext(NULL),
|
|
_pWebSocketContext(NULL),
|
|
_hWebSocketRequest(NULL),
|
|
_pHandler(NULL),
|
|
_dwOutstandingIo(0),
|
|
_fCleanupInProgress(FALSE),
|
|
_fIndicateCompletionToIis(FALSE),
|
|
_fHandleClosed(FALSE),
|
|
_fReceivedCloseMsg(FALSE)
|
|
{
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::WEBSOCKET_HANDLER");
|
|
|
|
InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000);
|
|
InsertRequest();
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::Terminate(
|
|
VOID
|
|
)
|
|
{
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::Terminate");
|
|
if (!_fHandleClosed)
|
|
{
|
|
RemoveRequest();
|
|
_fCleanupInProgress = TRUE;
|
|
|
|
if (_pHttpContext != NULL)
|
|
{
|
|
_pHttpContext->CancelIo();
|
|
_pHttpContext = NULL;
|
|
}
|
|
if (_hWebSocketRequest)
|
|
{
|
|
WinHttpCloseHandle(_hWebSocketRequest);
|
|
_hWebSocketRequest = NULL;
|
|
}
|
|
|
|
_pWebSocketContext = NULL;
|
|
DeleteCriticalSection(&_RequestLock);
|
|
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
//static
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::StaticInitialize(
|
|
BOOL fEnableReferenceCountTracing
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize structures required for idle connection cleanup.
|
|
|
|
--*/
|
|
{
|
|
if (!g_fWebSocketStaticInitialize)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
if (fEnableReferenceCountTracing)
|
|
{
|
|
//
|
|
// If tracing is enabled, keep track of all websocket requests
|
|
// for debugging purposes.
|
|
//
|
|
InitializeListHead (&sm_RequestsListHead);
|
|
sm_pTraceLog = CreateRefTraceLog( 10000, 0 );
|
|
}
|
|
|
|
InitializeSRWLock(&sm_RequestsListLock);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
WEBSOCKET_HANDLER::StaticTerminate(
|
|
VOID
|
|
)
|
|
{
|
|
if (!g_fWebSocketStaticInitialize)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (sm_pTraceLog)
|
|
{
|
|
DestroyRefTraceLog(sm_pTraceLog);
|
|
sm_pTraceLog = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::InsertRequest(
|
|
VOID
|
|
)
|
|
{
|
|
if (g_fEnableReferenceCountTracing)
|
|
{
|
|
AcquireSRWLockExclusive(&sm_RequestsListLock);
|
|
InsertTailList(&sm_RequestsListHead, &_listEntry);
|
|
ReleaseSRWLockExclusive( &sm_RequestsListLock);
|
|
}
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
WEBSOCKET_HANDLER::RemoveRequest(
|
|
VOID
|
|
)
|
|
{
|
|
if (g_fEnableReferenceCountTracing)
|
|
{
|
|
AcquireSRWLockExclusive(&sm_RequestsListLock);
|
|
RemoveEntryList(&_listEntry);
|
|
ReleaseSRWLockExclusive( &sm_RequestsListLock);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::IncrementOutstandingIo(
|
|
VOID
|
|
)
|
|
{
|
|
LONG dwOutstandingIo = InterlockedIncrement(&_dwOutstandingIo);
|
|
if (sm_pTraceLog)
|
|
{
|
|
WriteRefTraceLog(sm_pTraceLog, dwOutstandingIo, this);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::DecrementOutstandingIo(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Decrements outstanding IO count.
|
|
|
|
This indicates completion to IIS if all outstanding IO
|
|
has been completed, and a Cleanup was triggered for this
|
|
connection (denoted by _fIndicateCompletionToIis).
|
|
|
|
--*/
|
|
{
|
|
LONG dwOutstandingIo = InterlockedDecrement (&_dwOutstandingIo);
|
|
|
|
if (sm_pTraceLog)
|
|
{
|
|
WriteRefTraceLog(sm_pTraceLog, dwOutstandingIo, this);
|
|
}
|
|
|
|
if (dwOutstandingIo == 0 && _fIndicateCompletionToIis)
|
|
{
|
|
IndicateCompletionToIIS();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::IndicateCompletionToIIS(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Indicates completion to IIS.
|
|
|
|
This returns a Pending Status, so that forwarding handler has a chance
|
|
to do book keeping when request is finally done.
|
|
|
|
--*/
|
|
{
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::IndicateCompletionToIIS called %d", _dwOutstandingIo);
|
|
|
|
//
|
|
// close Websocket handle. This will triger a WinHttp callback
|
|
// on handle close, then let IIS pipeline continue.
|
|
// Make sure no pending IO as there is no IIS websocket cancelation,
|
|
// any unexpected callback will lead to AV. Revisit it once CanelOutGoingIO works
|
|
//
|
|
if (_hWebSocketRequest != NULL && _dwOutstandingIo == 0)
|
|
{
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::IndicateCompletionToIIS");
|
|
|
|
_pHandler->SetStatus(FORWARDER_DONE);
|
|
_fHandleClosed = TRUE;
|
|
WinHttpCloseHandle(_hWebSocketRequest);
|
|
_hWebSocketRequest = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::ProcessRequest(
|
|
FORWARDING_HANDLER *pHandler,
|
|
IHttpContext *pHttpContext,
|
|
HINTERNET hRequest,
|
|
BOOL* pfHandleCreated
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Entry point to WebSocket Handler:
|
|
|
|
This routine is called after the 101 response was successfully sent to
|
|
the client.
|
|
This routine get's a websocket handle to winhttp,
|
|
websocket handle to IIS's websocket context, and initiates IO
|
|
in these two endpoints.
|
|
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
//DWORD dwBuffSize = RECEIVE_BUFFER_SIZE;
|
|
|
|
*pfHandleCreated = FALSE;
|
|
_pHandler = pHandler;
|
|
|
|
EnterCriticalSection(&_RequestLock);
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::ProcessRequest");
|
|
|
|
//
|
|
// Cache the points to IHttpContext3
|
|
//
|
|
hr = HttpGetExtendedInterface(g_pHttpServer,
|
|
pHttpContext,
|
|
&_pHttpContext);
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Get pointer to IWebSocketContext for IIS websocket IO.
|
|
//
|
|
|
|
_pWebSocketContext = (IWebSocketContext *) _pHttpContext->
|
|
GetNamedContextContainer()->GetNamedContext(IIS_WEBSOCKET);
|
|
if ( _pWebSocketContext == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Get Handle to Winhttp's websocket context.
|
|
//
|
|
_hWebSocketRequest = WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade(
|
|
hRequest,
|
|
(DWORD_PTR) pHandler);
|
|
|
|
if (_hWebSocketRequest == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto Finished;
|
|
}
|
|
|
|
*pfHandleCreated = TRUE;
|
|
|
|
//
|
|
// Resize the send & receive buffers to be more conservative (and avoid DoS attacks).
|
|
// NOTE: The two WinHTTP options below were added for WinBlue, so we can't
|
|
// rely on their existence.
|
|
//
|
|
|
|
//if (!WinHttpSetOption(_hWebSocketRequest,
|
|
// WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE,
|
|
// &dwBuffSize,
|
|
// sizeof(dwBuffSize)))
|
|
//{
|
|
// DWORD dwRet = GetLastError();
|
|
// if ( dwRet != ERROR_WINHTTP_INVALID_OPTION )
|
|
// {
|
|
// hr = HRESULT_FROM_WIN32(dwRet);
|
|
// goto Finished;
|
|
// }
|
|
//}
|
|
|
|
//if (!WinHttpSetOption(_hWebSocketRequest,
|
|
// WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE,
|
|
// &dwBuffSize,
|
|
// sizeof(dwBuffSize)))
|
|
//{
|
|
// DWORD dwRet = GetLastError();
|
|
// if ( dwRet != ERROR_WINHTTP_INVALID_OPTION )
|
|
// {
|
|
// hr = HRESULT_FROM_WIN32(dwRet);
|
|
// goto Finished;
|
|
// }
|
|
//}
|
|
|
|
//
|
|
// Initiate Read on IIS
|
|
//
|
|
hr = DoIisWebSocketReceive();
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Initiate Read on WinHttp
|
|
//
|
|
|
|
hr = DoWinHttpWebSocketReceive();
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
LeaveCriticalSection(&_RequestLock);
|
|
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
LOG_ERRORF(L"Process Request Failed with HR=%08x", hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::DoIisWebSocketReceive(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates a websocket receive on the IIS Websocket Context.
|
|
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwBufferSize = RECEIVE_BUFFER_SIZE;
|
|
BOOL fUtf8Encoded;
|
|
BOOL fFinalFragment;
|
|
BOOL fClose;
|
|
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::DoIisWebSocketReceive");
|
|
|
|
IncrementOutstandingIo();
|
|
|
|
hr = _pWebSocketContext->ReadFragment(
|
|
&_IisReceiveBuffer,
|
|
&dwBufferSize,
|
|
TRUE,
|
|
&fUtf8Encoded,
|
|
&fFinalFragment,
|
|
&fClose,
|
|
OnReadIoCompletion,
|
|
this,
|
|
NULL);
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
DecrementOutstandingIo();
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates a websocket receive on WinHttp
|
|
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwError = NO_ERROR;
|
|
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive");
|
|
|
|
IncrementOutstandingIo();
|
|
|
|
dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketReceive(
|
|
_hWebSocketRequest,
|
|
&_WinHttpReceiveBuffer,
|
|
RECEIVE_BUFFER_SIZE,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
DecrementOutstandingIo();
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::DoIisWebSocketSend(
|
|
DWORD cbData,
|
|
WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates a websocket send on IIS
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fUtf8Encoded = FALSE;
|
|
BOOL fFinalFragment = FALSE;
|
|
BOOL fClose = FALSE;
|
|
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend %d", eBufferType);
|
|
|
|
if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE)
|
|
{
|
|
//
|
|
// Query Close Status from WinHttp
|
|
//
|
|
|
|
DWORD dwError = NO_ERROR;
|
|
USHORT uStatus;
|
|
DWORD dwReceived = 0;
|
|
STACK_STRU(strCloseReason, 128);
|
|
|
|
dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus(
|
|
_hWebSocketRequest,
|
|
&uStatus,
|
|
&_WinHttpReceiveBuffer,
|
|
RECEIVE_BUFFER_SIZE,
|
|
&dwReceived);
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Convert close reason to WCHAR
|
|
//
|
|
hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer,
|
|
dwReceived);
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
IncrementOutstandingIo();
|
|
//
|
|
// Backend end may start close hand shake first
|
|
// Need to indicate no more receive should be called on WinHttp connection
|
|
//
|
|
_fReceivedCloseMsg = TRUE;
|
|
_fIndicateCompletionToIis = TRUE;
|
|
|
|
//
|
|
// Send close to IIS.
|
|
//
|
|
hr = _pWebSocketContext->SendConnectionClose(
|
|
TRUE,
|
|
uStatus,
|
|
uStatus == 1005 ? NULL : strCloseReason.QueryStr(),
|
|
OnWriteIoCompletion,
|
|
this,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get equivalant flags for IIS API from buffer type.
|
|
//
|
|
|
|
WINHTTP_HELPER::GetFlagsFromBufferType(eBufferType,
|
|
&fUtf8Encoded,
|
|
&fFinalFragment,
|
|
&fClose);
|
|
|
|
IncrementOutstandingIo();
|
|
|
|
//
|
|
// Do the Send.
|
|
//
|
|
hr = _pWebSocketContext->WriteFragment(
|
|
&_WinHttpReceiveBuffer,
|
|
&cbData,
|
|
TRUE,
|
|
fUtf8Encoded,
|
|
fFinalFragment,
|
|
OnWriteIoCompletion,
|
|
this,
|
|
NULL);
|
|
}
|
|
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
DecrementOutstandingIo();
|
|
}
|
|
|
|
Finished:
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::DoWinHttpWebSocketSend(
|
|
DWORD cbData,
|
|
WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initiates a websocket send on WinHttp
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError = NO_ERROR;
|
|
HRESULT hr = S_OK;
|
|
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType);
|
|
|
|
if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE)
|
|
{
|
|
USHORT uStatus;
|
|
LPCWSTR pszReason;
|
|
STACK_STRA(strCloseReason, 128);
|
|
|
|
//
|
|
// Get Close status from IIS.
|
|
//
|
|
hr = _pWebSocketContext->GetCloseStatus(&uStatus,
|
|
&pszReason);
|
|
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Convert status to UTF8
|
|
//
|
|
hr = strCloseReason.CopyWToUTF8Unescaped(pszReason);
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
IncrementOutstandingIo();
|
|
|
|
//
|
|
// Send Close.
|
|
//
|
|
dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown(
|
|
_hWebSocketRequest,
|
|
uStatus,
|
|
strCloseReason.QueryCCH() == 0 ? NULL : (PVOID) strCloseReason.QueryStr(),
|
|
strCloseReason.QueryCCH());
|
|
|
|
if (dwError == ERROR_IO_PENDING)
|
|
{
|
|
//
|
|
// Call will complete asynchronously, return.
|
|
// ignore error.
|
|
//
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING");
|
|
|
|
dwError = NO_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if (dwError == NO_ERROR)
|
|
{
|
|
//
|
|
// Call completed synchronously.
|
|
//
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful.");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IncrementOutstandingIo();
|
|
|
|
dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend(
|
|
_hWebSocketRequest,
|
|
eBufferType,
|
|
cbData == 0 ? NULL : &_IisReceiveBuffer,
|
|
cbData
|
|
);
|
|
}
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
DecrementOutstandingIo();
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
WINAPI
|
|
WEBSOCKET_HANDLER::OnReadIoCompletion(
|
|
HRESULT hrError,
|
|
VOID * pvCompletionContext,
|
|
DWORD cbIO,
|
|
BOOL fUTF8Encoded,
|
|
BOOL fFinalFragment,
|
|
BOOL fClose
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Completion routine for Read's from IIS pipeline.
|
|
|
|
--*/
|
|
{
|
|
WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *)
|
|
pvCompletionContext;
|
|
|
|
pHandler->OnIisReceiveComplete(
|
|
hrError,
|
|
cbIO,
|
|
fUTF8Encoded,
|
|
fFinalFragment,
|
|
fClose
|
|
);
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
WINAPI
|
|
WEBSOCKET_HANDLER::OnWriteIoCompletion(
|
|
HRESULT hrError,
|
|
VOID * pvCompletionContext,
|
|
DWORD cbIO,
|
|
BOOL fUTF8Encoded,
|
|
BOOL fFinalFragment,
|
|
BOOL fClose
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Completion routine for Write's from IIS pipeline.
|
|
|
|
--*/
|
|
{
|
|
WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *)
|
|
pvCompletionContext;
|
|
|
|
UNREFERENCED_PARAMETER(fUTF8Encoded);
|
|
UNREFERENCED_PARAMETER(fFinalFragment);
|
|
UNREFERENCED_PARAMETER(fClose);
|
|
|
|
pHandler->OnIisSendComplete(
|
|
hrError,
|
|
cbIO
|
|
);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnWinHttpSendComplete(
|
|
WINHTTP_WEB_SOCKET_STATUS *
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Completion callback executed when a send to backend
|
|
server completes.
|
|
|
|
If the send was successful, issue the next read
|
|
on the client's endpoint.
|
|
|
|
++*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fLocked = FALSE;
|
|
CleanupReason cleanupReason = CleanupReasonUnknown;
|
|
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::OnWinHttpSendComplete");
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
EnterCriticalSection (&_RequestLock);
|
|
|
|
fLocked = TRUE;
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
//
|
|
// Data was successfully sent to backend.
|
|
// Initiate next receive from IIS.
|
|
//
|
|
|
|
hr = DoIisWebSocketReceive();
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
if (fLocked)
|
|
{
|
|
LeaveCriticalSection(&_RequestLock);
|
|
}
|
|
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
Cleanup (cleanupReason);
|
|
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr);
|
|
}
|
|
|
|
//
|
|
// The handler object can be gone after this call.
|
|
// do not reference it after this statement.
|
|
//
|
|
DecrementOutstandingIo();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnWinHttpShutdownComplete(
|
|
VOID
|
|
)
|
|
{
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::OnWinHttpShutdownComplete --%p", _pHandler);
|
|
|
|
DecrementOutstandingIo();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnWinHttpIoError(
|
|
WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus
|
|
)
|
|
{
|
|
HRESULT hr = HRESULT_FROM_WIN32(pCompletionStatus->AsyncResult.dwError);
|
|
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d",
|
|
hr, pCompletionStatus->AsyncResult.dwResult);
|
|
|
|
Cleanup(ServerDisconnect);
|
|
DecrementOutstandingIo();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnWinHttpReceiveComplete(
|
|
WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Completion callback executed when a receive completes
|
|
on the backend server winhttp endpoint.
|
|
|
|
Issue send on the Client(IIS) if the receive was
|
|
successful.
|
|
|
|
If the receive completed with zero bytes, that
|
|
indicates that the server has disconnected the connection.
|
|
Issue cleanup for the websocket handler.
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fLocked = FALSE;
|
|
CleanupReason cleanupReason = CleanupReasonUnknown;
|
|
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::OnWinHttpReceiveComplete --%p", _pHandler);
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
EnterCriticalSection(&_RequestLock);
|
|
|
|
fLocked = TRUE;
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
hr = DoIisWebSocketSend(
|
|
pCompletionStatus->dwBytesTransferred,
|
|
pCompletionStatus->eBufferType
|
|
);
|
|
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
cleanupReason = ClientDisconnect;
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
if (fLocked)
|
|
{
|
|
LeaveCriticalSection(&_RequestLock);
|
|
}
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
Cleanup (cleanupReason);
|
|
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr);
|
|
}
|
|
|
|
//
|
|
// The handler object can be gone after this call.
|
|
// do not reference it after this statement.
|
|
//
|
|
|
|
DecrementOutstandingIo();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnIisSendComplete(
|
|
HRESULT hrCompletion,
|
|
DWORD cbIo
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Completion callback executed when a send
|
|
completes from the client.
|
|
|
|
If send was successful,issue read on the
|
|
server endpoint, to continue the readloop.
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fLocked = FALSE;
|
|
CleanupReason cleanupReason = CleanupReasonUnknown;
|
|
|
|
UNREFERENCED_PARAMETER(cbIo);
|
|
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::OnIisSendComplete");
|
|
|
|
if (FAILED_LOG(hrCompletion))
|
|
{
|
|
hr = hrCompletion;
|
|
cleanupReason = ClientDisconnect;
|
|
goto Finished;
|
|
}
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
EnterCriticalSection(&_RequestLock);
|
|
fLocked = TRUE;
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Only call read if no close hand shake was received from backend
|
|
//
|
|
if (!_fReceivedCloseMsg)
|
|
{
|
|
//
|
|
// Write Completed, initiate next read from backend server.
|
|
//
|
|
hr = DoWinHttpWebSocketReceive();
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
cleanupReason = ServerDisconnect;
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
Finished:
|
|
if (fLocked)
|
|
{
|
|
LeaveCriticalSection(&_RequestLock);
|
|
}
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
Cleanup (cleanupReason);
|
|
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr);
|
|
}
|
|
|
|
//
|
|
// The handler object can be gone after this call.
|
|
// do not reference it after this statement.
|
|
//
|
|
|
|
DecrementOutstandingIo();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
WEBSOCKET_HANDLER::OnIisReceiveComplete(
|
|
HRESULT hrCompletion,
|
|
DWORD cbIO,
|
|
BOOL fUTF8Encoded,
|
|
BOOL fFinalFragment,
|
|
BOOL fClose
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Completion routine executed when a receive completes
|
|
from the client (IIS endpoint).
|
|
|
|
If the receive was successful, initiate a send on
|
|
the backend server (winhttp) endpoint.
|
|
|
|
If the receive failed, initiate cleanup.
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fLocked = FALSE;
|
|
CleanupReason cleanupReason = CleanupReasonUnknown;
|
|
WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType;
|
|
|
|
LOG_TRACE(L"WEBSOCKET_HANDLER::OnIisReceiveComplete");
|
|
|
|
if (FAILED_LOG(hrCompletion))
|
|
{
|
|
cleanupReason = ClientDisconnect;
|
|
hr = hrCompletion;
|
|
goto Finished;
|
|
}
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
EnterCriticalSection(&_RequestLock);
|
|
|
|
fLocked = TRUE;
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
//
|
|
// Get Buffer Type from flags.
|
|
//
|
|
|
|
WINHTTP_HELPER::GetBufferTypeFromFlags(fUTF8Encoded,
|
|
fFinalFragment,
|
|
fClose,
|
|
&BufferType);
|
|
|
|
//
|
|
// Initiate Send.
|
|
//
|
|
|
|
hr = DoWinHttpWebSocketSend(cbIO, BufferType);
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
cleanupReason = ServerDisconnect;
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
if (fLocked)
|
|
{
|
|
LeaveCriticalSection(&_RequestLock);
|
|
}
|
|
if (FAILED_LOG(hr))
|
|
{
|
|
Cleanup (cleanupReason);
|
|
|
|
LOG_ERRORF(L"WEBSOCKET_HANDLER::OnIisReceiveComplete failed with HR=%08x", hr);
|
|
}
|
|
|
|
//
|
|
// The handler object can be gone after this call.
|
|
// do not reference it after this statement.
|
|
//
|
|
|
|
DecrementOutstandingIo();
|
|
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
WEBSOCKET_HANDLER::Cleanup(
|
|
CleanupReason reason
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleanup function for the websocket handler.
|
|
|
|
Initiates cancelIo on the two IO endpoints:
|
|
IIS, WinHttp client.
|
|
|
|
Arguments:
|
|
CleanupReason
|
|
--*/
|
|
{
|
|
BOOL fLocked = FALSE;
|
|
LOG_TRACEF(L"WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason);
|
|
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
EnterCriticalSection(&_RequestLock);
|
|
|
|
fLocked = TRUE;
|
|
if (_fCleanupInProgress)
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
_fCleanupInProgress = TRUE;
|
|
|
|
_fIndicateCompletionToIis = TRUE;
|
|
|
|
//
|
|
// TODO:: Raise FREB event with cleanup reason.
|
|
//
|
|
if (reason == ClientDisconnect || reason == ServerStateUnavailable)
|
|
{
|
|
//
|
|
// Calling shutdown to notify the backend about disonnect
|
|
//
|
|
WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown(
|
|
_hWebSocketRequest,
|
|
1011, // indicate that a server is terminating the connection because it encountered
|
|
// an unexpected condition that prevent it from fulfilling the request
|
|
NULL, // Reason
|
|
0); // length og Reason
|
|
|
|
}
|
|
|
|
if (reason == ServerDisconnect || reason == ServerStateUnavailable)
|
|
{
|
|
_pHttpContext->CancelIo();
|
|
//
|
|
// CancelIo sometime may not be able to cannel pending websocket IO
|
|
// ResetConnection to force IISWebsocket module to release the pipeline
|
|
//
|
|
_pHttpContext->GetResponse()->ResetConnection();
|
|
}
|
|
|
|
Finished:
|
|
if (fLocked)
|
|
{
|
|
LeaveCriticalSection(&_RequestLock);
|
|
}
|
|
}
|