From 145eb4863e30c344c1fe39f154d82b4dd35a530a Mon Sep 17 00:00:00 2001 From: Ryan Hurey Date: Tue, 25 Nov 2025 19:13:47 -0800 Subject: [PATCH] feat: Restore Windows WebSocket functionality --- .../win_http_websockets_transport.hpp | 5 +- .../src/http/winhttp/win_http_websockets.cpp | 145 +++++++++++++----- 2 files changed, 109 insertions(+), 41 deletions(-) diff --git a/sdk/core/azure-core/inc/azure/core/http/websockets/win_http_websockets_transport.hpp b/sdk/core/azure-core/inc/azure/core/http/websockets/win_http_websockets_transport.hpp index 344f32dc7..2ad25060b 100644 --- a/sdk/core/azure-core/inc/azure/core/http/websockets/win_http_websockets_transport.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/websockets/win_http_websockets_transport.hpp @@ -16,6 +16,7 @@ #include "azure/core/internal/unique_handle.hpp" #include #include +#include namespace Azure { namespace Core { namespace Http { namespace WebSockets { @@ -24,13 +25,13 @@ namespace Azure { namespace Core { namespace Http { namespace WebSockets { */ class WinHttpWebSocketTransport : public WebSocketTransport, public WinHttpTransport { - Azure::Core::_internal::UniqueHandle m_socketHandle; // HINTERNET is void* + Azure::Core::_internal::UniqueHandle m_socketHandle; std::mutex m_sendMutex; std::mutex m_receiveMutex; // Fixed method signature to match current base class void OnUpgradedConnection( - std::unique_ptr<_detail::WinHttpRequest> const& request) const override; + std::unique_ptr<_detail::WinHttpRequest> const& request) override; public: /** diff --git a/sdk/core/azure-core/src/http/winhttp/win_http_websockets.cpp b/sdk/core/azure-core/src/http/winhttp/win_http_websockets.cpp index 9a6bc6f16..d1791f02f 100644 --- a/sdk/core/azure-core/src/http/winhttp/win_http_websockets.cpp +++ b/sdk/core/azure-core/src/http/winhttp/win_http_websockets.cpp @@ -8,6 +8,7 @@ #include "azure/core/internal/diagnostics/log.hpp" #include "azure/core/platform.hpp" #include "azure/core/request_failed_exception.hpp" +#include "win_http_request.hpp" #if defined(AZ_PLATFORM_POSIX) #include // for poll() @@ -21,22 +22,23 @@ #endif #include #include // for WSAPoll(); +#include #endif #include namespace Azure { namespace Core { namespace Http { namespace WebSockets { void WinHttpWebSocketTransport::OnUpgradedConnection( - std::unique_ptr<_detail::WinHttpRequest> const& request) const + std::unique_ptr<_detail::WinHttpRequest> const& request) { - // TODO: Need to get HINTERNET handle from WinHttpRequest - // For now, this is a placeholder implementation to fix compilation - // The actual implementation needs to: - // 1. Get HINTERNET handle from request object - // 2. Call WinHttpWebSocketCompleteUpgrade - // 3. Store the WebSocket handle - - throw Azure::Core::RequestFailedException("WebSocket upgrade not yet implemented on Windows"); + // Convert the request handle into a WebSocket handle for us to use later. + m_socketHandle = Azure::Core::_internal::UniqueHandle( + WinHttpWebSocketCompleteUpgrade(request->GetRequestHandle(), 0)); + + if (!m_socketHandle) + { + GetErrorAndThrow("Error Upgrading HttpRequest handle to WebSocket handle."); + } } std::unique_ptr WinHttpWebSocketTransport::Send( @@ -50,8 +52,10 @@ namespace Azure { namespace Core { namespace Http { namespace WebSockets { * @brief Close the WebSocket cleanly. */ void WinHttpWebSocketTransport::Close() { - // TODO: Implement proper WebSocket close when Windows integration is complete - // m_socketHandle.reset(); + if (m_socketHandle) + { + m_socketHandle.reset(); + } } // Native WebSocket support methods. @@ -72,25 +76,22 @@ namespace Azure { namespace Core { namespace Http { namespace WebSockets { { context.ThrowIfCancelled(); - // TODO: Windows WebSocket API integration needed - // auto err = WinHttpWebSocketClose( - // m_socketHandle.get(), - // status, - // disconnectReason.empty() - // ? nullptr - // : reinterpret_cast(const_cast(disconnectReason.c_str())), - // static_cast(disconnectReason.size())); - // if (err != 0) - // { - // throw Azure::Core::RequestFailedException("WinHttpWebSocketClose() failed"); - // } - throw Azure::Core::RequestFailedException("Windows WebSocket native close not yet implemented"); + auto err = WinHttpWebSocketClose( + m_socketHandle.get(), + status, + disconnectReason.empty() + ? nullptr + : reinterpret_cast(const_cast(disconnectReason.c_str())), + static_cast(disconnectReason.size())); + if (err != 0) + { + GetErrorAndThrow("WinHttpWebSocketClose() failed", err); + } context.ThrowIfCancelled(); - // TODO: Windows WebSocket API integration needed - // auto closeInformation = NativeGetCloseSocketInformation(context); - (void)context; + // Make sure that the server responds gracefully to the close request. + auto closeInformation = NativeGetCloseSocketInformation(context); // The server should return the same status we sent. if (closeInformation.CloseReason != status) @@ -112,10 +113,28 @@ namespace Azure { namespace Core { namespace Http { namespace WebSockets { WinHttpWebSocketTransport::NativeWebSocketCloseInformation WinHttpWebSocketTransport::NativeGetCloseSocketInformation(Azure::Core::Context const& context) { - (void)context; - // TODO: Windows WebSocket API integration needed - throw Azure::Core::RequestFailedException("Windows WebSocket get close info not yet implemented"); - } + context.ThrowIfCancelled(); + uint16_t closeStatus = 0; + + // Handle missing constant definition in older Windows SDKs + #ifndef WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH + #define WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH 123 + #endif + + char closeReason[WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH]{}; + DWORD closeReasonLength; + + auto err = WinHttpWebSocketQueryCloseStatus( + m_socketHandle.get(), + &closeStatus, + closeReason, + WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH, + &closeReasonLength); + if (err != 0) + { + GetErrorAndThrow("WinHttpWebSocketQueryCloseStatus() failed", err); + } + return NativeWebSocketCloseInformation{closeStatus, std::string(closeReason)}; } /** @@ -153,19 +172,67 @@ namespace Azure { namespace Core { namespace Http { namespace WebSockets { "Unknown frame type: " + std::to_string(static_cast(frameType))); break; } - // TODO: Windows WebSocket API integration needed - (void)frameType; - (void)frameData; - (void)context; - throw Azure::Core::RequestFailedException("Windows WebSocket send frame not yet implemented"); + + // Lock the socket to prevent concurrent writes. WinHTTP gets annoyed if + // there are multiple WinHttpWebSocketSend requests outstanding. + std::lock_guard lock(m_sendMutex); + auto err = WinHttpWebSocketSend( + m_socketHandle.get(), + bufferType, + reinterpret_cast(const_cast(frameData.data())), + static_cast(frameData.size())); + if (err != 0) + { + GetErrorAndThrow("WinHttpWebSocketSend() failed", err); + } } WinHttpWebSocketTransport::NativeWebSocketReceiveInformation WinHttpWebSocketTransport::NativeReceiveFrame(Azure::Core::Context const& context) { - (void)context; - // TODO: Windows WebSocket API integration needed - throw Azure::Core::RequestFailedException("Windows WebSocket receive frame not yet implemented"); + context.ThrowIfCancelled(); + + WINHTTP_WEB_SOCKET_BUFFER_TYPE bufferType; + NativeWebSocketFrameType frameTypeReceived; + DWORD bufferBytesRead; + std::vector buffer(128); + + std::lock_guard lock(m_receiveMutex); + + auto err = WinHttpWebSocketReceive( + m_socketHandle.get(), + reinterpret_cast(buffer.data()), + static_cast(buffer.size()), + &bufferBytesRead, + &bufferType); + if (err != 0 && err != ERROR_INSUFFICIENT_BUFFER) + { + GetErrorAndThrow("WinHttpWebSocketReceive() failed", err); + } + buffer.resize(bufferBytesRead); + + switch (bufferType) + { + case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: + frameTypeReceived = NativeWebSocketFrameType::Text; + break; + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: + frameTypeReceived = NativeWebSocketFrameType::Binary; + break; + case WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE: + frameTypeReceived = NativeWebSocketFrameType::BinaryFragment; + break; + case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: + frameTypeReceived = NativeWebSocketFrameType::TextFragment; + break; + case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE: + frameTypeReceived = NativeWebSocketFrameType::Closed; + break; + default: + throw std::runtime_error("Unknown frame type: " + std::to_string(bufferType)); + break; + } + return NativeWebSocketReceiveInformation{frameTypeReceived, buffer}; } }}}} // namespace Azure::Core::Http::WebSockets