HRESULT HC_WEBSOCKET::Connect()

in Source/WebSocket/hcwebsocket.cpp [37:166]


HRESULT HC_WEBSOCKET::Connect(
    _In_z_ const char* uri,
    _In_z_ const char* subProtocol,
    _Inout_ XAsyncBlock* asyncBlock
) noexcept
{
    auto httpSingleton = get_http_singleton();
    if (!httpSingleton)
    {
        return E_HC_NOT_INITIALISED;
    }

    if (m_state != State::Initial)
    {
        return E_UNEXPECTED;
    }

    m_uri = uri;
    m_subProtocol = subProtocol;

    WebSocketPerformInfo const& info = httpSingleton->m_websocketPerform;

    auto connectFunc = info.connect;
    if (connectFunc != nullptr)
    {
        try
        {
            // Trap the result of the connect before returning to client. Otherwise we have no way of knowing
            // if we are in a connected state. This also helps us handle proper disconnection if we were connecting,
            // when the client closed their handle.

            ZeroMemory(&m_connectAsyncBlock, sizeof(XAsyncBlock));
            m_connectAsyncBlock.queue = asyncBlock->queue;
            m_connectAsyncBlock.context = this;
            m_connectAsyncBlock.callback = [](XAsyncBlock* async)
            {
                auto thisPtr{ static_cast<HC_WEBSOCKET*>(async->context) };
                HRESULT hr = HCGetWebSocketConnectResult(async, &thisPtr->m_connectResult);

                // We auto disconnect if the client has already closed all handles.
                bool doDisconnect{ false };

                // We release the provider's reference if we don't expect any more callbacks from the WebSocket provider.
                bool doDecRef { false };
                {
                    std::lock_guard<std::recursive_mutex> lock{ thisPtr->m_mutex };

                    if (thisPtr->m_state != State::Connecting)
                    {
                        hr = E_UNEXPECTED;
                    }

                    if (SUCCEEDED(hr) && SUCCEEDED(thisPtr->m_connectResult.errorCode))
                    {
                        if (thisPtr->m_clientRefCount > 0)
                        {
                            thisPtr->m_state = State::Connected;
                        }
                        else
                        {
                            doDisconnect = true;
                        }
                    }
                    else
                    {
                        HC_TRACE_INFORMATION(WEBSOCKET, "Websocket connection attempt failed");

                        // Release providers ref if connect fails. We do not expect a close event in this case.
                        thisPtr->m_state = State::Disconnected;
                        doDecRef = true;
                    }
                }

                if (doDisconnect)
                {
                    thisPtr->Disconnect();
                }

                if (doDecRef)
                {
                    thisPtr->DecRef();
                }

                XAsyncComplete(thisPtr->m_clientConnectAsyncBlock, hr, sizeof(WebSocketCompletionResult));
            };

            m_clientConnectAsyncBlock = asyncBlock;
            XAsyncBegin(m_clientConnectAsyncBlock, this, (void*)HCWebSocketConnectAsync, __FUNCTION__,
                [](XAsyncOp op, const XAsyncProviderData* data)
                {
                    auto thisPtr{ static_cast<HC_WEBSOCKET*>(data->context) };

                    switch (op)
                    {
                    case XAsyncOp::DoWork: return E_PENDING;
                    case XAsyncOp::GetResult:
                    {
                        auto clientResult{ reinterpret_cast<WebSocketCompletionResult*>(data->buffer) };
                        *clientResult = thisPtr->m_connectResult;
                    }
                    default: return S_OK;
                    }
                }
            );

            HRESULT hr = connectFunc(uri, subProtocol, this, &m_connectAsyncBlock, info.context, httpSingleton->m_performEnv.get());

            if (SUCCEEDED(hr))
            {
                {
                    std::lock_guard<std::recursive_mutex> lock{ m_mutex };
                    m_state = State::Connecting;
                }
                // Add a ref for the provider. This guarantees the HC_WEBSOCKET is alive until disconnect.
                AddRef();
            }
            return hr;
        }
        catch (...)
        {
            HC_TRACE_ERROR(WEBSOCKET, "HCWebSocketConnect [ID %llu]: failed", TO_ULL(id));
            return E_FAIL;
        }
    }
    else
    {
        HC_TRACE_ERROR(WEBSOCKET, "HC_WEBSOCKET::Connect [ID %llu]: Websocket connect implementation not found!", TO_ULL(id));
        return E_UNEXPECTED;
    }
}