diff --git a/.vscode/cspell.json b/.vscode/cspell.json index fbd5d8dda6..3629c403e4 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -73,6 +73,7 @@ "HKEY", "HRESULT", "IMDS", + "immutability", "Intel", "itfactor", "iusg", diff --git a/cmake-modules/AddGoogleTest.cmake b/cmake-modules/AddGoogleTest.cmake index 508a867a1f..d273d565b0 100644 --- a/cmake-modules/AddGoogleTest.cmake +++ b/cmake-modules/AddGoogleTest.cmake @@ -78,7 +78,8 @@ macro(add_gtest TESTNAME) else() gtest_discover_tests(${TESTNAME} TEST_PREFIX "${TESTNAME}." - PROPERTIES FOLDER "Tests") + PROPERTIES FOLDER "Tests" + DISCOVERY_TIMEOUT 600) endif() else() add_test(${TESTNAME} ${TESTNAME}) diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp index b847ddb3c1..959703daba 100644 --- a/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +#include "azure/core/internal/diagnostics/global_exception.hpp" +#include "azure/core/platform.hpp" + +#include #include int main(int argc, char** argv) @@ -10,6 +14,17 @@ int main(int argc, char** argv) // End users need to decide if SIGPIPE should be ignored or not. signal(SIGPIPE, SIG_IGN); #endif + // Declare a signal handler to report unhandled exceptions on Windows - this is not needed for + // other +// OS's as they will print the exception to stderr in their terminate() function. +#if defined(AZ_PLATFORM_WINDOWS) + // Ensure that all calls to abort() no longer pop up a modal dialog on Windows. +#if defined(_DEBUG) && defined(_MSC_VER) + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); +#endif + + signal(SIGABRT, Azure::Core::Diagnostics::_internal::GlobalExceptionHandler::HandleSigAbort); +#endif // AZ_PLATFORM_WINDOWS testing::InitGoogleTest(&argc, argv); auto r = RUN_ALL_TESTS(); diff --git a/sdk/core/azure-core/CMakeLists.txt b/sdk/core/azure-core/CMakeLists.txt index 1cb7ebae1b..04eea7a218 100644 --- a/sdk/core/azure-core/CMakeLists.txt +++ b/sdk/core/azure-core/CMakeLists.txt @@ -54,10 +54,11 @@ endif() if(BUILD_TRANSPORT_WINHTTP) SET(WIN_TRANSPORT_ADAPTER_SRC src/http/winhttp/win_http_transport.cpp + src/http/winhttp/win_http_request.hpp ) - SET(WIN_TRANSPORT_ADAPTER_INC + SET(WIN_TRANSPORT_ADAPTER_INC inc/azure/core/http/win_http_transport.hpp - ) + ) endif() set( diff --git a/sdk/core/azure-core/inc/azure/core/http/win_http_transport.hpp b/sdk/core/azure-core/inc/azure/core/http/win_http_transport.hpp index d28e551bef..9ce13dd502 100644 --- a/sdk/core/azure-core/inc/azure/core/http/win_http_transport.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/win_http_transport.hpp @@ -5,7 +5,6 @@ * @file * @brief #Azure::Core::Http::HttpTransport implementation via WinHTTP. */ -// cspell:words HCERTIFICATECHAIN PCCERT CCERT HCERTCHAINENGINE HCERTSTORE #pragma once @@ -14,8 +13,7 @@ #include "azure/core/http/policies/policy.hpp" #include "azure/core/http/transport.hpp" #include "azure/core/internal/unique_handle.hpp" - -#include +#include "azure/core/platform.hpp" #if defined(AZ_PLATFORM_WINDOWS) #if !defined(WIN32_LEAN_AND_MEAN) @@ -57,53 +55,8 @@ namespace Azure { namespace Core { constexpr static size_t DefaultUploadChunkSize = 1024 * 64; constexpr static size_t MaximumUploadChunkSize = 1024 * 1024; - class WinHttpStream final : public Azure::Core::IO::BodyStream { - private: - Azure::Core::_internal::UniqueHandle m_requestHandle; - bool m_isEOF; - - /** - * @brief This is a copy of the value of an HTTP response header `content-length`. The value - * is received as string and parsed to size_t. This field avoids parsing the string header - * every time from HTTP RawResponse. - * - * @remark This value is also used to avoid trying to read more data from network than what - * we are expecting to. - * - * @remark A value of -1 means the transfer encoding was chunked. - * - */ - int64_t m_contentLength; - - int64_t m_streamTotalRead; - - /** - * @brief Implement #Azure::Core::IO::BodyStream::OnRead(). Calling this function pulls data - * from the wire. - * - * @param context A context to control the request lifetime. - * @param buffer Buffer where data from wire is written to. - * @param count The number of bytes to read from the network. - * @return The actual number of bytes read from the network. - */ - size_t OnRead(uint8_t* buffer, size_t count, Azure::Core::Context const& context) override; - - public: - WinHttpStream( - Azure::Core::_internal::UniqueHandle& requestHandle, - int64_t contentLength) - : m_requestHandle(std::move(requestHandle)), m_contentLength(contentLength), - m_isEOF(false), m_streamTotalRead(0) - { - } - - /** - * @brief Implement #Azure::Core::IO::BodyStream length. - * - * @return The size of the payload. - */ - int64_t Length() const override { return this->m_contentLength; } - }; + // Forward declaration for WinHttpRequest. + class WinHttpRequest; } // namespace _detail /** @@ -173,70 +126,22 @@ namespace Azure { namespace Core { class WinHttpTransport : public HttpTransport { private: WinHttpTransportOptions m_options; - - // This should remain immutable and not be modified after calling the ctor, to avoid threading - // issues. - Azure::Core::_internal::UniqueHandle m_sessionHandle; - bool m_requestHandleClosed{false}; + // m_sessionhandle is const to ensure immutability. + const Azure::Core::_internal::UniqueHandle m_sessionHandle; Azure::Core::_internal::UniqueHandle CreateSessionHandle(); Azure::Core::_internal::UniqueHandle CreateConnectionHandle( Azure::Core::Url const& url, Azure::Core::Context const& context); - Azure::Core::_internal::UniqueHandle CreateRequestHandle( + std::unique_ptr<_detail::WinHttpRequest> CreateRequestHandle( Azure::Core::_internal::UniqueHandle const& connectionHandle, Azure::Core::Url const& url, Azure::Core::Http::HttpMethod const& method); - void Upload( - Azure::Core::_internal::UniqueHandle const& requestHandle, - Azure::Core::Http::Request& request, - Azure::Core::Context const& context); - void SendRequest( - Azure::Core::_internal::UniqueHandle const& requestHandle, - Azure::Core::Http::Request& request, - Azure::Core::Context const& context); - void ReceiveResponse( - Azure::Core::_internal::UniqueHandle const& requestHandle, - Azure::Core::Context const& context); - int64_t GetContentLength( - Azure::Core::_internal::UniqueHandle const& requestHandle, - HttpMethod requestMethod, - HttpStatusCode responseStatusCode); - std::unique_ptr SendRequestAndGetResponse( - Azure::Core::_internal::UniqueHandle& requestHandle, - HttpMethod requestMethod); - - /* - * Callback from WinHTTP called after the TLS certificates are received when the caller sets - * expected TLS root certificates. - */ - static void CALLBACK StatusCallback( - HINTERNET hInternet, - DWORD_PTR dwContext, - DWORD dwInternetStatus, - LPVOID lpvStatusInformation, - DWORD dwStatusInformationLength) noexcept; - /* - * Callback from WinHTTP called after the TLS certificates are received when the caller sets - * expected TLS root certificates. - */ - void OnHttpStatusOperation(HINTERNET hInternet, DWORD dwInternetStatus); - /* - * Adds the specified trusted certificates to the specified certificate store. - */ - bool AddCertificatesToStore( - std::vector const& trustedCertificates, - HCERTSTORE const hCertStore); - /* - * Verifies that the certificate context is in the trustedCertificates set of certificates. - */ - bool VerifyCertificatesInChain( - std::vector const& trustedCertificates, - PCCERT_CONTEXT serverCertificate); // Callback to allow a derived transport to extract the request handle. Used for WebSocket // transports. - virtual void OnUpgradedConnection(Azure::Core::_internal::UniqueHandle const&){}; + virtual void OnUpgradedConnection(std::unique_ptr<_detail::WinHttpRequest> const&){}; + /** * @brief Throw an exception based on the Win32 Error code * @@ -266,8 +171,8 @@ namespace Azure { namespace Core { WinHttpTransport(Azure::Core::Http::Policies::TransportOptions const& options); /** - * @brief Implements the HTTP transport interface to send an HTTP Request and produce an HTTP - * RawResponse. + * @brief Implements the HTTP transport interface to send an HTTP Request and produce an + * HTTP RawResponse. * * @param context A context to control the request lifetime. * @param request an HTTP request to be send. @@ -279,7 +184,7 @@ namespace Azure { namespace Core { // [Core Guidelines C.35: "A base class destructor should be either public // and virtual or protected and // non-virtual"](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c35-a-base-class-destructor-should-be-either-public-and-virtual-or-protected-and-non-virtual) - virtual ~WinHttpTransport() = default; + virtual ~WinHttpTransport(); }; } // namespace Http diff --git a/sdk/core/azure-core/inc/azure/core/internal/diagnostics/global_exception.hpp b/sdk/core/azure-core/inc/azure/core/internal/diagnostics/global_exception.hpp new file mode 100644 index 0000000000..d051efb97a --- /dev/null +++ b/sdk/core/azure-core/inc/azure/core/internal/diagnostics/global_exception.hpp @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once +#include +#include + +namespace Azure { namespace Core { namespace Diagnostics { namespace _internal { + /** + * @brief Global Exception handler used for test collateral. This is not intended to be used by + * any non-test code. + * + * The GlobalExceptionHandler class is used to catch any unhandled exceptions and report them as a + * part of test collateral, it is intended to be called as a SIGABRT handler set by: + * + * ``` + * signal(SIGABRT, Azure::Core::Diagnostics::_internal::GlobalExceptionHandler); + * ``` + * + */ + struct GlobalExceptionHandler + { + static void HandleSigAbort(int) + { + // Rethrow any exceptions on the current stack - this will cause any pending exceptions to + // be thrown so we can catch them and report them to the caller. This is needed because the + // terminate() function on Windows calls abort() which normally pops up a modal dialog box + // after which it terminates the application without reporting the exception. + try + { + throw; + } + catch (std::exception const& ex) + { + std::cerr << "SIGABRT raised, exception: " << ex.what() << std::endl; + } + } + }; +}}}} // namespace Azure::Core::Diagnostics::_internal diff --git a/sdk/core/azure-core/src/http/curl/curl.cpp b/sdk/core/azure-core/src/http/curl/curl.cpp index 3263b8cc5e..93f9a467c2 100644 --- a/sdk/core/azure-core/src/http/curl/curl.cpp +++ b/sdk/core/azure-core/src/http/curl/curl.cpp @@ -132,7 +132,7 @@ int pollSocketUntilEventOrTimeout( auto deadline = now + std::chrono::milliseconds(timeout); while (now < deadline) { - // check cancelation + // Before doing any work, check to make sure that the context hasn't already been cancelled. context.ThrowIfCancelled(); int pollTimeoutMs = static_cast( std::min( diff --git a/sdk/core/azure-core/src/http/retry_policy.cpp b/sdk/core/azure-core/src/http/retry_policy.cpp index 84b037e66c..d2066b6ec3 100644 --- a/sdk/core/azure-core/src/http/retry_policy.cpp +++ b/sdk/core/azure-core/src/http/retry_policy.cpp @@ -176,6 +176,7 @@ std::unique_ptr RetryPolicy::Send( // we proceed immediately if it is 0. if (retryAfter.count() > 0) { + // Before sleeping, check to make sure that the context hasn't already been cancelled. context.ThrowIfCancelled(); std::this_thread::sleep_for(retryAfter); } diff --git a/sdk/core/azure-core/src/http/transport_policy.cpp b/sdk/core/azure-core/src/http/transport_policy.cpp index ec90e0253c..119dc31683 100644 --- a/sdk/core/azure-core/src/http/transport_policy.cpp +++ b/sdk/core/azure-core/src/http/transport_policy.cpp @@ -97,6 +97,7 @@ std::unique_ptr TransportPolicy::Send( NextHttpPolicy, Context const& context) const { + // Before doing any work, check to make sure that the context hasn't already been cancelled. context.ThrowIfCancelled(); /* diff --git a/sdk/core/azure-core/src/http/winhttp/win_http_request.hpp b/sdk/core/azure-core/src/http/winhttp/win_http_request.hpp new file mode 100644 index 0000000000..41c3b694ec --- /dev/null +++ b/sdk/core/azure-core/src/http/winhttp/win_http_request.hpp @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +// cspell:words PCCERT HCERTSTORE + +/** + * @file + * @brief #Azure::Core::Http::HttpTransport request support classes. + */ + +#pragma once + +#include "azure/core/http/win_http_transport.hpp" +#include "azure/core/url.hpp" +#include +#include +#include +#include +#include + +namespace Azure { namespace Core { namespace Http { namespace _detail { + + class WinHttpRequest; + /** + * @brief An outstanding WinHTTP action. This object is used to process asynchronous WinHTTP + * actions. + * + * The WinHttpRequest object has a WinHttpAction associated with it to convert asynchronous + * WinHTTP operations to synchronous operations. + * + */ + class WinHttpAction final { + + // Containing HTTP request, used during the status operation callback. + WinHttpRequest* const m_httpRequest{}; + wil::unique_event m_actionCompleteEvent; + // Mutex protecting all mutable members of the class. + std::mutex m_actionCompleteMutex; + DWORD m_expectedStatus{}; + DWORD m_stowedError{}; + DWORD_PTR m_stowedErrorInformation{}; + DWORD m_bytesAvailable{}; + + /* + * Callback from WinHTTP called after the TLS certificates are received when the caller sets + * expected TLS root certificates. + */ + static void CALLBACK StatusCallback( + HINTERNET hInternet, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength) noexcept; + + /* + * Callback from WinHTTP called after the TLS certificates are received when the caller sets + * expected TLS root certificates. + */ + void OnHttpStatusOperation( + HINTERNET hInternet, + DWORD internetStatus, + LPVOID statusInformation, + DWORD statusInformationLength); + + public: + /** + * @brief Create a new WinHttpAction object associated with a specific WinHttpRequest. + * + * @param request Http Request associated with the action. + */ + WinHttpAction(WinHttpRequest* request) + // Create a non-inheritable anonymous manual reset event intialized as unset. + : m_httpRequest(request), m_actionCompleteEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr)) + { + if (!m_actionCompleteEvent) + { + throw std::runtime_error("Error creating Action Complete Event."); + } + } + + /** + * Register the WinHTTP Status callback used by the action. + * + * @param internetHandle HINTERNET to register the callback. + * @returns The status of the operation. + */ + bool RegisterWinHttpStatusCallback( + Azure::Core::_internal::UniqueHandle const& internetHandle); + + /** + * @brief WaitForAction - Waits for an action to complete. + * + * @remarks The WaitForAction method waits until an action initiated by the `callback` function + * has completed. Every pollDuration milliseconds, it checks to see if the context specified for + * the request has been cancelled (or times out). + * + * @param initiateAction - Function called to initiate an action. Always called in the waiting + * thread. + * @param expectedCallbackStatus - Wait until the expectedStatus event occurs. + * @param pollDuration - The time to wait for a ping to complete. Defaults to 800ms because it + * seems like a reasonable minimum responsiveness value (also this is the default retry + * policy delay). + * @param context - Context for the operation. + * + * @returns true if the action completed normally, false if there was an error. + + * @remarks If there is an error, the caller can determine the error code by calling + * GetStowedError() and GetStowedErrorInformation() + */ + bool WaitForAction( + std::function initiateAction, + DWORD expectedCallbackStatus, + Azure::Core::Context const& context, + Azure::DateTime::duration const& pollDuration = std::chrono::milliseconds(800)); + + /** + * @brief Notify a caller that the action has completed successfully. + */ + void CompleteAction(); + + /** + * @brief Notify a caller that the action has completed successfully and reflect the bytes + * available + */ + void CompleteActionWithData(DWORD bytesAvailable); + + /** + * @brief Notify a caller that the action has completed with an error and save the error code + * and information. + */ + void CompleteActionWithError(DWORD_PTR stowedErrorInformation, DWORD stowedError); + DWORD GetStowedError(); + DWORD_PTR GetStowedErrorInformation(); + DWORD GetBytesAvailable(); + }; + + /** + * @brief A WinHttpRequest object encapsulates an HTTP operation. + */ + class WinHttpRequest final { + bool m_requestHandleClosed{false}; + Azure::Core::_internal::UniqueHandle m_requestHandle; + std::unique_ptr m_httpAction; + std::vector m_expectedTlsRootCertificates; + + /* + * Adds the specified trusted certificates to the specified certificate store. + */ + bool AddCertificatesToStore( + std::vector const& trustedCertificates, + HCERTSTORE const hCertStore) const; + /* + * Verifies that the certificate context is in the trustedCertificates set of certificates. + */ + bool VerifyCertificatesInChain( + std::vector const& trustedCertificates, + PCCERT_CONTEXT serverCertificate) const; + /** + * @brief Throw an exception based on the Win32 Error code + * + * @param exceptionMessage Message describing error. + * @param error Win32 Error code. + */ + void GetErrorAndThrow(const std::string& exceptionMessage, DWORD error = GetLastError()) const; + + public: + WinHttpRequest( + Azure::Core::_internal::UniqueHandle const& connectionHandle, + Azure::Core::Url const& url, + Azure::Core::Http::HttpMethod const& method, + WinHttpTransportOptions const& options); + + ~WinHttpRequest(); + + void Upload(Azure::Core::Http::Request& request, Azure::Core::Context const& context); + void SendRequest(Azure::Core::Http::Request& request, Azure::Core::Context const& context); + void ReceiveResponse(Azure::Core::Context const& context); + int64_t GetContentLength(HttpMethod requestMethod, HttpStatusCode responseStatusCode); + std::unique_ptr SendRequestAndGetResponse(HttpMethod requestMethod); + size_t ReadData(uint8_t* buffer, size_t bufferSize, Azure::Core::Context const& context); + void EnableWebSocketsSupport(); + void HandleExpectedTlsRootCertificates(HINTERNET hInternet); + }; + + class WinHttpStream final : public Azure::Core::IO::BodyStream { + private: + std::unique_ptr<_detail::WinHttpRequest> m_requestHandle; + bool m_isEOF; + + /** + * @brief This is a copy of the value of an HTTP response header `content-length`. The value + * is received as string and parsed to size_t. This field avoids parsing the string header + * every time from HTTP RawResponse. + * + * @remark This value is also used to avoid trying to read more data from network than what + * we are expecting to. + * + * @remark A value of -1 means the transfer encoding was chunked. + * + */ + int64_t m_contentLength; + + int64_t m_streamTotalRead; + + /** + * @brief Implement #Azure::Core::IO::BodyStream::OnRead(). Calling this function pulls data + * from the wire. + * + * @param context A context to control the request lifetime. + * @param buffer Buffer where data from wire is written to. + * @param count The number of bytes to read from the network. + * @return The actual number of bytes read from the network. + */ + size_t OnRead(uint8_t* buffer, size_t count, Azure::Core::Context const& context) override; + + public: + WinHttpStream(std::unique_ptr<_detail::WinHttpRequest>& requestHandle, int64_t contentLength) + : m_requestHandle(std::move(requestHandle)), m_contentLength(contentLength), m_isEOF(false), + m_streamTotalRead(0) + { + } + + /** + * @brief Implement #Azure::Core::IO::BodyStream length. + * + * @return The size of the payload. + */ + int64_t Length() const override { return this->m_contentLength; } + }; + +}}}} // namespace Azure::Core::Http::_detail diff --git a/sdk/core/azure-core/src/http/winhttp/win_http_transport.cpp b/sdk/core/azure-core/src/http/winhttp/win_http_transport.cpp index 2d51637cf5..79a968e6b3 100644 --- a/sdk/core/azure-core/src/http/winhttp/win_http_transport.cpp +++ b/sdk/core/azure-core/src/http/winhttp/win_http_transport.cpp @@ -12,7 +12,9 @@ #if defined(BUILD_TRANSPORT_WINHTTP_ADAPTER) #include "azure/core/http/win_http_transport.hpp" +#include "win_http_request.hpp" #endif + #include #include #include @@ -212,9 +214,9 @@ std::string GetHeadersAsString(Azure::Core::Http::Request const& request) } // namespace // For each certificate specified in trustedCertificate, add to certificateStore. -bool WinHttpTransport::AddCertificatesToStore( +bool _detail::WinHttpRequest::AddCertificatesToStore( std::vector const& trustedCertificates, - HCERTSTORE certificateStore) + HCERTSTORE certificateStore) const { for (auto const& trustedCertificate : trustedCertificates) { @@ -236,9 +238,9 @@ bool WinHttpTransport::AddCertificatesToStore( // VerifyCertificateInChain determines whether the certificate in serverCertificate // chains up to one of the certificates represented by trustedCertificate or not. -bool WinHttpTransport::VerifyCertificatesInChain( +bool _detail::WinHttpRequest::VerifyCertificatesInChain( std::vector const& trustedCertificates, - PCCERT_CONTEXT serverCertificate) + PCCERT_CONTEXT serverCertificate) const { if ((trustedCertificates.empty()) || !serverCertificate) { @@ -327,121 +329,349 @@ bool WinHttpTransport::VerifyCertificatesInChain( return true; } -/** - * Called by WinHTTP when sending a request to the server. This callback allows us to inspect the - * TLS certificate before sending it to the server. - */ -void WinHttpTransport::StatusCallback( - HINTERNET hInternet, - DWORD_PTR dwContext, - DWORD dwInternetStatus, - LPVOID, - DWORD) noexcept +namespace { + +// If the `internetStatus` value has `id` bit set, then append the name of `id` to the string `rv`. +#define APPEND_ENUM_STRING(id) \ + if (internetStatus & (id)) \ + { \ + rv += #id " "; \ + } +std::string InternetStatusToString(DWORD internetStatus) { - // If we're called before our context has been set (on Open and Close callbacks), ignore the - // status callback. - if (dwContext == 0) + std::string rv; + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_RESOLVING_NAME); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_NAME_RESOLVED); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SENDING_REQUEST); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_REQUEST_SENT); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_HANDLE_CREATED); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_DETECTING_PROXY); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_REDIRECT); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SECURE_FAILURE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_READ_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_REQUEST_ERROR); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE); + // For this is not defined on the Win2022 Azure DevOps image, so manually expand it. + // APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE); + if (internetStatus & 0x08000000) { - return; + rv += std::string("WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE") + " "; } + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE); + APPEND_ENUM_STRING(WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE); + return rv; +} +#undef APPEND_ENUM_STRING - try +std::string GetErrorMessage(DWORD error) +{ + std::string errorMessage = " Error Code: " + std::to_string(error); + + char* errorMsg = nullptr; + if (FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER, + GetModuleHandle("winhttp.dll"), + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&errorMsg), + 0, + nullptr) + != 0) + { + // Use a unique_ptr to manage the lifetime of errorMsg. + std::unique_ptr errorString(errorMsg, &LocalFree); + errorMsg = nullptr; + + errorMessage += ": "; + errorMessage += errorString.get(); + } + errorMessage += '.'; + return errorMessage; +} +} // namespace + +namespace Azure { namespace Core { namespace Http { namespace _detail { + + bool WinHttpAction::RegisterWinHttpStatusCallback( + Azure::Core::_internal::UniqueHandle const& internetHandle) { - WinHttpTransport* httpTransport = reinterpret_cast(dwContext); - httpTransport->OnHttpStatusOperation(hInternet, dwInternetStatus); + return ( + WinHttpSetStatusCallback( + internetHandle.get(), + &WinHttpAction::StatusCallback, + WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, + 0) + != WINHTTP_INVALID_STATUS_CALLBACK); } - catch (Azure::Core::RequestFailedException& rfe) + + /** + * Wait for an action to complete. + * + */ + bool WinHttpAction::WaitForAction( + std::function initiateAction, + DWORD expectedCallbackStatus, + Azure::Core::Context const& context, + Azure::DateTime::duration const& pollInterval) { - // If an exception is thrown in the handler, log the error and terminate the connection. - Log::Write( - Logger::Level::Error, - "Request Failed Exception Thrown: " + std::string(rfe.what()) + rfe.Message); - WinHttpCloseHandle(hInternet); + // Before doing any work, check to make sure that the context hasn't already been cancelled. + context.ThrowIfCancelled(); + + // By definition, there cannot be any actions outstanding at this point because we have not + // yet called initiateAction. So it's safe to reset our state here. + ResetEvent(m_actionCompleteEvent.get()); + m_expectedStatus = expectedCallbackStatus; + m_stowedError = 0; + m_stowedErrorInformation = 0; + m_bytesAvailable = 0; + + // Call the provided callback to start the WinHTTP action. + initiateAction(); + + DWORD waitResult; + do + { + waitResult = WaitForSingleObject( + m_actionCompleteEvent.get(), + static_cast( + std::chrono::duration_cast(pollInterval).count())); + if (waitResult == WAIT_TIMEOUT) + { + // If the request was cancelled while we were waiting, throw an exception. + context.ThrowIfCancelled(); + } + else if (waitResult != WAIT_OBJECT_0) + { + return false; + } + } while (waitResult != WAIT_OBJECT_0); + if (m_stowedError != NO_ERROR) + { + return false; + } + return true; } - catch (std::exception& ex) + + void WinHttpAction::CompleteAction() { - // If an exception is thrown in the handler, log the error and terminate the connection. - Log::Write(Logger::Level::Error, "Exception Thrown: " + std::string(ex.what())); + auto scope_exit{m_actionCompleteEvent.SetEvent_scope_exit()}; + } + void WinHttpAction::CompleteActionWithData(DWORD bytesAvailable) + { + // Note that the order of scope_exit and lock is important - this ensures that scope_exit is + // destroyed *after* lock is destroyed, ensuring that the event is not set to the signalled + // state before the lock is released. + auto scope_exit{m_actionCompleteEvent.SetEvent_scope_exit()}; + std::unique_lock lock(m_actionCompleteMutex); + m_bytesAvailable = bytesAvailable; + } + void WinHttpAction::CompleteActionWithError(DWORD_PTR stowedErrorInformation, DWORD stowedError) + { + // Note that the order of scope_exit and lock is important - this ensures that scope_exit is + // destroyed *after* lock is destroyed, ensuring that the event is not set to the signalled + // state before the lock is released. + auto scope_exit{m_actionCompleteEvent.SetEvent_scope_exit()}; + std::unique_lock lock(m_actionCompleteMutex); + m_stowedErrorInformation = stowedErrorInformation; + m_stowedError = stowedError; } -} -/** - * @brief HTTP Callback to enable private certificate checks. - * - * This method is called by WinHTTP when a certificate is received. This method is called multiple - * times based on the state of the TLS connection. We are only interested in - * WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, which is called during the TLS handshake. - * - * When called, we verify that the certificate chain sent from the server contains the certificate - * the HTTP client was configured with. If it is, we accept the connection, if it is not, - * we abort the connection, closing the incoming request handle. - */ -void WinHttpTransport::OnHttpStatusOperation(HINTERNET hInternet, DWORD dwInternetStatus) -{ - if (dwInternetStatus != WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) + DWORD WinHttpAction::GetStowedError() { - if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) + std::unique_lock lock(m_actionCompleteMutex); + return m_stowedError; + } + DWORD_PTR WinHttpAction::GetStowedErrorInformation() + { + std::unique_lock lock(m_actionCompleteMutex); + return m_stowedErrorInformation; + } + DWORD WinHttpAction::GetBytesAvailable() + { + std::unique_lock lock(m_actionCompleteMutex); + return m_bytesAvailable; + } + + /** + * Called by WinHTTP when sending a request to the server. This callback allows us to inspect + * the TLS certificate before sending it to the server. + */ + void WinHttpAction::StatusCallback( + HINTERNET hInternet, + DWORD_PTR dwContext, + DWORD internetStatus, + LPVOID statusInformation, + DWORD statusInformationLength) noexcept + { + // If we're called before our context has been set (on Open and Close callbacks), ignore the + // status callback. + if (dwContext == 0) { - Log::Write(Logger::Level::Error, "Security failure. :("); + return; } - // Silently ignore if there's any statuses we get that we can't handle - return; - } - // We will only set the Status callback if a root certificate has been set. - AZURE_ASSERT(!m_options.ExpectedTlsRootCertificates.empty()); + try + { + WinHttpAction* httpAction = reinterpret_cast(dwContext); + httpAction->OnHttpStatusOperation( + hInternet, internetStatus, statusInformation, statusInformationLength); + } + catch (Azure::Core::RequestFailedException const& rfe) + { + // If an exception is thrown in the handler, log the error and terminate the connection. + Log::Write( + Logger::Level::Error, + "Request Failed Exception Thrown: " + std::string(rfe.what()) + rfe.Message); + WinHttpCloseHandle(hInternet); + } + catch (std::exception const& ex) + { + // If an exception is thrown in the handler, log the error and terminate the connection. + Log::Write(Logger::Level::Error, "Exception Thrown: " + std::string(ex.what())); + } + } - // Ask WinHTTP for the server certificate - this won't be valid outside a status callback. - wil::unique_cert_context serverCertificate; + /** + * @brief HTTP Callback to enable private certificate checks. + * + * This method is called by WinHTTP when a certificate is received. This method is called + * multiple times based on the state of the TLS connection. + * + * Special consideration for the WINHTTP_CALLBACK_STATUS_SENDING_REQUEST - this callback is + * called during the TLS connection - if a TLS root certificate is configured, we verify that the + * certificate chain sent from the server contains the certificate the HTTP client was configured + * with. If it is, we accept the connection, if it is not, we abort the connection, closing the + * incoming request handle. + */ + void WinHttpAction::OnHttpStatusOperation( + HINTERNET hInternet, + DWORD internetStatus, + LPVOID statusInformation, + DWORD statusInformationLength) { - DWORD bufferLength = sizeof(PCCERT_CONTEXT); - if (!WinHttpQueryOption( - hInternet, - WINHTTP_OPTION_SERVER_CERT_CONTEXT, - reinterpret_cast(serverCertificate.addressof()), - &bufferLength)) + Log::Write( + Logger::Level::Informational, + "Status operation: " + std::to_string(internetStatus) + "(" + + InternetStatusToString(internetStatus) + ")"); + if (internetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { - GetErrorAndThrow("Could not retrieve TLS server certificate."); + Log::Write(Logger::Level::Error, "Security failure. :("); + } + else if (internetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) + { + WINHTTP_ASYNC_RESULT* asyncResult = static_cast(statusInformation); + Log::Write( + Logger::Level::Error, + "Request error. " + GetErrorMessage(asyncResult->dwError) + " " + + std::to_string(asyncResult->dwResult)); + CompleteActionWithError(asyncResult->dwResult, asyncResult->dwError); + } + else if (internetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) + { + // We will only set the Status callback if a root certificate has been set. There is no action + // which needs to be completed for this notification. + m_httpRequest->HandleExpectedTlsRootCertificates(hInternet); + } + else if (internetStatus == m_expectedStatus) + { + switch (internetStatus) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + // A WinHttpSendRequest API call has completed, complete the current action. + CompleteAction(); + break; + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + // A WinHttpWriteData call has completed, complete the current action. + CompleteAction(); + break; + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + // Headers for an HTTP response are available, complete the current action. + CompleteAction(); + break; + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + // A WinHttpReadData call has completed. Complete the current action, including the amount + // of data read. + CompleteActionWithData(statusInformationLength); + break; + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + // An HINTERNET handle is closing, complete the outstanding close request. + Log::Write( + Logger::Level::Verbose, "Closing handle; completing outstanding Close request"); + CompleteAction(); + break; + default: + Log::Write( + Logger::Level::Error, + "Received expected status " + InternetStatusToString(internetStatus) + + " but it was not handled."); + break; + } } } - if (!VerifyCertificatesInChain(m_options.ExpectedTlsRootCertificates, serverCertificate.get())) + void WinHttpRequest::HandleExpectedTlsRootCertificates(HINTERNET hInternet) { - Log::Write(Logger::Level::Error, "Server certificate is not trusted. Aborting HTTP request"); + if (!m_expectedTlsRootCertificates.empty()) + { + // Ask WinHTTP for the server certificate - this won't be valid outside a status callback. + wil::unique_cert_context serverCertificate; + { + DWORD bufferLength = sizeof(PCCERT_CONTEXT); + if (!WinHttpQueryOption( + hInternet, + WINHTTP_OPTION_SERVER_CERT_CONTEXT, + reinterpret_cast(serverCertificate.addressof()), + &bufferLength)) + { + GetErrorAndThrow("Could not retrieve TLS server certificate."); + } + } - // To signal to caller that the request is to be terminated, the callback closes the handle. - // This ensures that no message is sent to the server. - WinHttpCloseHandle(hInternet); + if (!VerifyCertificatesInChain(m_expectedTlsRootCertificates, serverCertificate.get())) + { + Log::Write( + Logger::Level::Error, "Server certificate is not trusted. Aborting HTTP request"); - // To avoid a double free of this handle record that we've - // already closed the handle. - m_requestHandleClosed = true; - } -} + // To signal to caller that the request is to be terminated, the callback closes the + // handle. This ensures that no message is sent to the server. + WinHttpCloseHandle(hInternet); -void WinHttpTransport::GetErrorAndThrow(const std::string& exceptionMessage, DWORD error) -{ - std::string errorMessage = exceptionMessage + " Error Code: " + std::to_string(error); + // To avoid a double free of this handle record that we've + // already closed the handle. + m_requestHandleClosed = true; - char* errorMsg = nullptr; - if (FormatMessage( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER, - GetModuleHandle("winhttp.dll"), - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&errorMsg), - 0, - nullptr) - != 0) + // And we're done processing the request, return because there's nothing + // else to do. + return; + } + } + } + + void WinHttpRequest::GetErrorAndThrow(const std::string& exceptionMessage, DWORD error) const { - // Use a unique_ptr to manage the lifetime of errorMsg. - std::unique_ptr errorString(errorMsg, &LocalFree); - errorMsg = nullptr; + std::string errorMessage = exceptionMessage + GetErrorMessage(error); - errorMessage += ": "; - errorMessage += errorString.get(); + throw Azure::Core::Http::TransportException(errorMessage); } - errorMessage += '.'; +}}}} // namespace Azure::Core::Http::_detail + +void WinHttpTransport::GetErrorAndThrow(const std::string& exceptionMessage, DWORD error) +{ + std::string errorMessage = exceptionMessage + GetErrorMessage(error); throw Azure::Core::Http::TransportException(errorMessage); } @@ -458,7 +688,7 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateSessionH : WINHTTP_ACCESS_TYPE_NO_PROXY), WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, - 0)); + WINHTTP_FLAG_ASYNC)); // All requests on this session are performed asynchronously. if (!sessionHandle) { @@ -495,21 +725,6 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateSessionH GetErrorAndThrow("Error while enforcing TLS 1.2 for connection request."); } - if (!m_options.ExpectedTlsRootCertificates.empty()) - { - - // Set the callback function to be called when a server certificate is received. - if (WinHttpSetStatusCallback( - sessionHandle.get(), - &WinHttpTransport::StatusCallback, - WINHTTP_CALLBACK_FLAG_SEND_REQUEST /* WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS*/, - 0) - == WINHTTP_INVALID_STATUS_CALLBACK) - { - GetErrorAndThrow("Error while setting up the status callback."); - } - } - return sessionHandle; } @@ -563,6 +778,8 @@ WinHttpTransport::WinHttpTransport( { } +WinHttpTransport::~WinHttpTransport() = default; + Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateConnectionHandle( Azure::Core::Url const& url, Azure::Core::Context const& context) @@ -570,6 +787,7 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateConnecti // If port is 0, i.e. INTERNET_DEFAULT_PORT, it uses port 80 for HTTP and port 443 for HTTPS. uint16_t port = url.GetPort(); + // Before doing any work, check to make sure that the context hasn't already been cancelled. context.ThrowIfCancelled(); // Specify an HTTP server. @@ -595,10 +813,24 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateConnecti return rv; } -Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateRequestHandle( +void _detail::WinHttpRequest::EnableWebSocketsSupport() +{ +#pragma warning(push) + // warning C6387: _Param_(3) could be '0'. +#pragma warning(disable : 6387) + if (!WinHttpSetOption(m_requestHandle.get(), WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0)) +#pragma warning(pop) + { + GetErrorAndThrow("Error while Enabling WebSocket upgrade."); + } +} + +_detail::WinHttpRequest::WinHttpRequest( Azure::Core::_internal::UniqueHandle const& connectionHandle, Azure::Core::Url const& url, - Azure::Core::Http::HttpMethod const& method) + Azure::Core::Http::HttpMethod const& method, + WinHttpTransportOptions const& options) + : m_expectedTlsRootCertificates(options.ExpectedTlsRootCertificates) { const std::string& path = url.GetRelativeUrl(); HttpMethod requestMethod = method; @@ -609,7 +841,7 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateRequestH url.GetScheme(), WebSocketScheme)); // Create an HTTP request handle. - Azure::Core::_internal::UniqueHandle request(WinHttpOpenRequest( + m_requestHandle.reset(WinHttpOpenRequest( connectionHandle.get(), HttpMethodToWideString(requestMethod).c_str(), path.empty() ? NULL : StringToWideString(path).c_str(), // Name of the target resource of @@ -618,7 +850,7 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateRequestH WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, // No media types are accepted by the client requestSecureHttp ? WINHTTP_FLAG_SECURE : 0)); // Uses secure transaction semantics (SSL/TLS) - if (!request) + if (!m_requestHandle) { // Errors include: // ERROR_WINHTTP_INCORRECT_HANDLE_TYPE @@ -638,73 +870,106 @@ Azure::Core::_internal::UniqueHandle WinHttpTransport::CreateRequestH // Note: If/When TLS client certificate support is added to the pipeline, this line may need to // be revisited. if (!WinHttpSetOption( - request.get(), WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) + m_requestHandle.get(), + WINHTTP_OPTION_CLIENT_CERT_CONTEXT, + WINHTTP_NO_CLIENT_CERT_CONTEXT, + 0)) { GetErrorAndThrow("Error while setting client cert context to ignore."); } } - if (!m_options.ProxyInformation.empty()) + if (!options.ProxyInformation.empty()) { WINHTTP_PROXY_INFO proxyInfo{}; - std::wstring proxyWide{StringToWideString(m_options.ProxyInformation)}; + std::wstring proxyWide{StringToWideString(options.ProxyInformation)}; proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; proxyInfo.lpszProxy = const_cast(proxyWide.c_str()); proxyInfo.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS; - if (!WinHttpSetOption(request.get(), WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo))) + if (!WinHttpSetOption( + m_requestHandle.get(), WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo))) { GetErrorAndThrow("Error while setting Proxy information."); } } - if (m_options.ProxyUserName.HasValue() || m_options.ProxyPassword.HasValue()) + if (options.ProxyUserName.HasValue() || options.ProxyPassword.HasValue()) { if (!WinHttpSetCredentials( - request.get(), + m_requestHandle.get(), WINHTTP_AUTH_TARGET_PROXY, WINHTTP_AUTH_SCHEME_BASIC, - StringToWideString(m_options.ProxyUserName.Value()).c_str(), - StringToWideString(m_options.ProxyPassword.Value()).c_str(), + StringToWideString(options.ProxyUserName.Value()).c_str(), + StringToWideString(options.ProxyPassword.Value()).c_str(), 0)) { GetErrorAndThrow("Error while setting Proxy credentials."); } } - if (m_options.IgnoreUnknownCertificateAuthority || !m_options.ExpectedTlsRootCertificates.empty()) + if (options.IgnoreUnknownCertificateAuthority || !options.ExpectedTlsRootCertificates.empty()) { auto option = SECURITY_FLAG_IGNORE_UNKNOWN_CA; - if (!WinHttpSetOption(request.get(), WINHTTP_OPTION_SECURITY_FLAGS, &option, sizeof(option))) + if (!WinHttpSetOption( + m_requestHandle.get(), WINHTTP_OPTION_SECURITY_FLAGS, &option, sizeof(option))) { GetErrorAndThrow("Error while setting ignore unknown server certificate."); } } - if (m_options.EnableCertificateRevocationListCheck) + if (options.EnableCertificateRevocationListCheck) { DWORD value = WINHTTP_ENABLE_SSL_REVOCATION; - if (!WinHttpSetOption(request.get(), WINHTTP_OPTION_ENABLE_FEATURE, &value, sizeof(value))) + if (!WinHttpSetOption( + m_requestHandle.get(), WINHTTP_OPTION_ENABLE_FEATURE, &value, sizeof(value))) { GetErrorAndThrow("Error while enabling CRL validation."); } } + // Set the callback function to be called whenever the state of the request handle changes. + m_httpAction = std::make_unique<_detail::WinHttpAction>(this); + + if (!m_httpAction->RegisterWinHttpStatusCallback(m_requestHandle)) + { + GetErrorAndThrow("Error while setting up the status callback."); + } +} + +/* + * Destructor for WinHTTP request. Closes the request handle. + */ +_detail::WinHttpRequest::~WinHttpRequest() +{ + if (!m_requestHandleClosed) + { + Log::Write( + Logger::Level::Verbose, "WinHttpRequest::~WinHttpRequest. Closing handle synchronously."); + + // Close the outstanding request handle, waiting until the HANDLE_CLOSING status is received. + m_httpAction->WaitForAction( + [this]() { m_requestHandle.reset(); }, + WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING, + Azure::Core::Context{}); + } +} + +std::unique_ptr<_detail::WinHttpRequest> WinHttpTransport::CreateRequestHandle( + Azure::Core::_internal::UniqueHandle const& connectionHandle, + Azure::Core::Url const& url, + Azure::Core::Http::HttpMethod const& method) +{ + auto request{std::make_unique<_detail::WinHttpRequest>(connectionHandle, url, method, m_options)}; // If we are supporting WebSockets, then let WinHTTP know that it should // prepare to upgrade the HttpRequest to a WebSocket. -#pragma warning(push) -// warning C6387: _Param_(3) could be '0'. -#pragma warning(disable : 6387) - if (HasWebSocketSupport() - && !WinHttpSetOption(request.get(), WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0)) -#pragma warning(pop) + if (HasWebSocketSupport()) { - GetErrorAndThrow("Error while Enabling WebSocket upgrade."); + request->EnableWebSocketsSupport(); } return request; } // For PUT/POST requests, send additional data using WinHttpWriteData. -void WinHttpTransport::Upload( - Azure::Core::_internal::UniqueHandle const& requestHandle, +void _detail::WinHttpRequest::Upload( Azure::Core::Http::Request& request, Azure::Core::Context const& context) { @@ -729,22 +994,27 @@ void WinHttpTransport::Upload( DWORD dwBytesWritten = 0; - context.ThrowIfCancelled(); + if (!m_httpAction->WaitForAction( + [&]() { // Write data to the server. + if (!WinHttpWriteData( + m_requestHandle.get(), + unique_buffer.get(), + static_cast(rawRequestLen), + &dwBytesWritten)) + { + GetErrorAndThrow("Error while uploading/sending data."); + } + }, + WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, + context)) - // Write data to the server. - if (!WinHttpWriteData( - requestHandle.get(), - unique_buffer.get(), - static_cast(rawRequestLen), - &dwBytesWritten)) { - GetErrorAndThrow("Error while uploading/sending data."); + GetErrorAndThrow("Error sending HTTP request asynchronously", m_httpAction->GetStowedError()); } } } -void WinHttpTransport::SendRequest( - Azure::Core::_internal::UniqueHandle const& requestHandle, +void _detail::WinHttpRequest::SendRequest( Azure::Core::Http::Request& request, Azure::Core::Context const& context) { @@ -764,78 +1034,110 @@ void WinHttpTransport::SendRequest( int64_t streamLength = request.GetBodyStream()->Length(); - context.ThrowIfCancelled(); - - // Send a request. - if (!WinHttpSendRequest( - requestHandle.get(), - requestHeaders.size() == 0 ? WINHTTP_NO_ADDITIONAL_HEADERS : encodedHeaders.c_str(), - encodedHeadersLength, - WINHTTP_NO_REQUEST_DATA, - 0, - streamLength > 0 ? static_cast(streamLength) : 0, - reinterpret_cast(this))) + try { - // Errors include: - // ERROR_WINHTTP_CANNOT_CONNECT - // ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED - // ERROR_WINHTTP_CONNECTION_ERROR - // ERROR_WINHTTP_INCORRECT_HANDLE_STATE - // ERROR_WINHTTP_INCORRECT_HANDLE_TYPE - // ERROR_WINHTTP_INTERNAL_ERROR - // ERROR_WINHTTP_INVALID_URL - // ERROR_WINHTTP_LOGIN_FAILURE - // ERROR_WINHTTP_NAME_NOT_RESOLVED - // ERROR_WINHTTP_OPERATION_CANCELLED - // ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW - // ERROR_WINHTTP_SECURE_FAILURE - // ERROR_WINHTTP_SHUTDOWN - // ERROR_WINHTTP_TIMEOUT - // ERROR_WINHTTP_UNRECOGNIZED_SCHEME - // ERROR_NOT_ENOUGH_MEMORY - // ERROR_INVALID_PARAMETER - // ERROR_WINHTTP_RESEND_REQUEST - GetErrorAndThrow("Error while sending a request."); - } + if (!m_httpAction->WaitForAction( + [&]() { + { + // Send a request. + // NB: DO NOT CHANGE THE TYPE OF THE CONTEXT PARAMETER WITHOUT UPDATING THE + // HttpAction::StatusCallback method. + if (!WinHttpSendRequest( + m_requestHandle.get(), + requestHeaders.size() == 0 ? WINHTTP_NO_ADDITIONAL_HEADERS + : encodedHeaders.c_str(), + encodedHeadersLength, + WINHTTP_NO_REQUEST_DATA, + 0, + streamLength > 0 ? static_cast(streamLength) : 0, + reinterpret_cast( + m_httpAction + .get()))) // Context for WinHTTP status callbacks for this request. + { + // Errors include: + // ERROR_WINHTTP_CANNOT_CONNECT + // ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED + // ERROR_WINHTTP_CONNECTION_ERROR + // ERROR_WINHTTP_INCORRECT_HANDLE_STATE + // ERROR_WINHTTP_INCORRECT_HANDLE_TYPE + // ERROR_WINHTTP_INTERNAL_ERROR + // ERROR_WINHTTP_INVALID_URL + // ERROR_WINHTTP_LOGIN_FAILURE + // ERROR_WINHTTP_NAME_NOT_RESOLVED + // ERROR_WINHTTP_OPERATION_CANCELLED + // ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW + // ERROR_WINHTTP_SECURE_FAILURE + // ERROR_WINHTTP_SHUTDOWN + // ERROR_WINHTTP_TIMEOUT + // ERROR_WINHTTP_UNRECOGNIZED_SCHEME + // ERROR_NOT_ENOUGH_MEMORY + // ERROR_INVALID_PARAMETER + // ERROR_WINHTTP_RESEND_REQUEST + GetErrorAndThrow("Error while sending a request."); + } + } + }, + WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, + context)) + { + GetErrorAndThrow( + "Error while waiting for a send to complete.", m_httpAction->GetStowedError()); + } - // Chunked transfer encoding is not supported and the content length needs to be known up front. - if (streamLength == -1) - { - throw Azure::Core::Http::TransportException( - "When uploading data, the body stream must have a known length."); - } + // Chunked transfer encoding is not supported and the content length needs to be known up + // front. + if (streamLength == -1) + { + throw Azure::Core::Http::TransportException( + "When uploading data, the body stream must have a known length."); + } - if (streamLength > 0) + if (streamLength > 0) + { + Upload(request, context); + } + } + catch (TransportException const&) { - Upload(requestHandle, request, context); + // If there was a TLS validation error, then we will have closed the request handle + // during the TLS validation callback. So if an exception was thrown, if we force closed the + // request handle, clear the handle in the requestHandle to prevent a double free. + if (m_requestHandleClosed) + { + m_requestHandle.release(); + } + throw; } } -void WinHttpTransport::ReceiveResponse( - Azure::Core::_internal::UniqueHandle const& requestHandle, - Azure::Core::Context const& context) +void _detail::WinHttpRequest::ReceiveResponse(Azure::Core::Context const& context) { - context.ThrowIfCancelled(); - // Wait to receive the response to the HTTP request initiated by WinHttpSendRequest. // When WinHttpReceiveResponse completes successfully, the status code and response headers have // been received. - if (!WinHttpReceiveResponse(requestHandle.get(), NULL)) + if (!m_httpAction->WaitForAction( + [this]() { + if (!WinHttpReceiveResponse(m_requestHandle.get(), NULL)) + { + // Errors include: + // ERROR_WINHTTP_CANNOT_CONNECT + // ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW + // ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED + // ... + // ERROR_WINHTTP_TIMEOUT + // ERROR_WINHTTP_UNRECOGNIZED_SCHEME + // ERROR_NOT_ENOUGH_MEMORY + GetErrorAndThrow("Error while receiving a response."); + } + }, + WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, + context)) { - // Errors include: - // ERROR_WINHTTP_CANNOT_CONNECT - // ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW - // ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED - // ... - // ERROR_WINHTTP_TIMEOUT - // ERROR_WINHTTP_UNRECOGNIZED_SCHEME - // ERROR_NOT_ENOUGH_MEMORY - GetErrorAndThrow("Error while receiving a response."); + GetErrorAndThrow("Error while receiving a response.", m_httpAction->GetStowedError()); } } -int64_t WinHttpTransport::GetContentLength( - Azure::Core::_internal::UniqueHandle const& requestHandle, +int64_t _detail::WinHttpRequest::GetContentLength( HttpMethod requestMethod, HttpStatusCode responseStatusCode) { @@ -852,7 +1154,7 @@ int64_t WinHttpTransport::GetContentLength( if (requestMethod != HttpMethod::Head && responseStatusCode != HttpStatusCode::NoContent) { if (!WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwContentLength, @@ -870,15 +1172,14 @@ int64_t WinHttpTransport::GetContentLength( return contentLength; } -std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( - Azure::Core::_internal::UniqueHandle& requestHandle, +std::unique_ptr _detail::WinHttpRequest::SendRequestAndGetResponse( HttpMethod requestMethod) { // First, use WinHttpQueryHeaders to obtain the size of the buffer. // The call is expected to fail since no destination buffer is provided. DWORD sizeOfHeaders = 0; if (WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, NULL, @@ -903,7 +1204,7 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( // Now, use WinHttpQueryHeaders to retrieve all the headers. // Each header is terminated by "\0". An additional "\0" terminates the list of headers. if (!WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, outputBuffer.data(), @@ -923,7 +1224,7 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( // Get the HTTP version. if (!WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_VERSION, WINHTTP_HEADER_NAME_BY_INDEX, outputBuffer.data(), @@ -946,7 +1247,7 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( // Get the status code as a number. if (!WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, @@ -967,7 +1268,7 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( if (majorVersion == 1) { if (WinHttpQueryHeaders( - requestHandle.get(), + m_requestHandle.get(), WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, outputBuffer.data(), @@ -992,14 +1293,29 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( SetHeaders(responseHeaders, rawResponse); - if (HasWebSocketSupport() && (httpStatusCode == HttpStatusCode::SwitchingProtocols)) + return rawResponse; +} + +std::unique_ptr WinHttpTransport::Send(Request& request, Context const& context) +{ + Azure::Core::_internal::UniqueHandle connectionHandle + = CreateConnectionHandle(request.GetUrl(), context); + std::unique_ptr<_detail::WinHttpRequest> requestHandle( + CreateRequestHandle(connectionHandle, request.GetUrl(), request.GetMethod())); + + requestHandle->SendRequest(request, context); + requestHandle->ReceiveResponse(context); + + auto rawResponse{requestHandle->SendRequestAndGetResponse(request.GetMethod())}; + if (rawResponse && HasWebSocketSupport() + && (rawResponse->GetStatusCode() == HttpStatusCode::SwitchingProtocols)) { OnUpgradedConnection(requestHandle); } else { int64_t contentLength - = GetContentLength(requestHandle, requestMethod, rawResponse->GetStatusCode()); + = requestHandle->GetContentLength(request.GetMethod(), rawResponse->GetStatusCode()); rawResponse->SetBodyStream( std::make_unique<_detail::WinHttpStream>(requestHandle, contentLength)); @@ -1007,32 +1323,53 @@ std::unique_ptr WinHttpTransport::SendRequestAndGetResponse( return rawResponse; } -std::unique_ptr WinHttpTransport::Send(Request& request, Context const& context) +size_t _detail::WinHttpRequest::ReadData( + uint8_t* buffer, + size_t count, + Azure::Core::Context const& context) { - Azure::Core::_internal::UniqueHandle connectionHandle - = CreateConnectionHandle(request.GetUrl(), context); - Azure::Core::_internal::UniqueHandle requestHandle - = CreateRequestHandle(connectionHandle, request.GetUrl(), request.GetMethod()); - try + DWORD numberOfBytesRead = 0; + if (!m_httpAction->WaitForAction( + [&]() { + if (!WinHttpReadData( + this->m_requestHandle.get(), + (LPVOID)(buffer), + static_cast(count), + &numberOfBytesRead)) + { + // Errors include: + // ERROR_WINHTTP_CONNECTION_ERROR + // ERROR_WINHTTP_INCORRECT_HANDLE_STATE + // ERROR_WINHTTP_INCORRECT_HANDLE_TYPE + // ERROR_WINHTTP_INTERNAL_ERROR + // ERROR_WINHTTP_OPERATION_CANCELLED + // ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW + // ERROR_WINHTTP_TIMEOUT + // ERROR_NOT_ENOUGH_MEMORY + + DWORD error = GetLastError(); + throw Azure::Core::Http::TransportException( + "Error while reading available data from the wire. Error Code: " + + std::to_string(error) + "."); + } + Log::Write( + Logger::Level::Verbose, + "Read Data read from wire. Size: " + std::to_string(numberOfBytesRead) + "."); + }, + WINHTTP_CALLBACK_STATUS_READ_COMPLETE, + context)) { - SendRequest(requestHandle, request, context); + GetErrorAndThrow("Error sending HTTP request asynchronously", m_httpAction->GetStowedError()); } - catch (TransportException&) + if (numberOfBytesRead == 0) { - // If there was a TLS validation error, then we will have closed the request handle - // during the TLS validation callback. So if an exception was thrown, if we force closed the - // request handle, clear the handle in the requestHandle to prevent a double free. - if (m_requestHandleClosed) - { - requestHandle.release(); - } - - throw; + numberOfBytesRead = m_httpAction->GetBytesAvailable(); } - ReceiveResponse(requestHandle, context); + Log::Write( + Logger::Level::Verbose, "ReadData returned size: " + std::to_string(numberOfBytesRead) + "."); - return SendRequestAndGetResponse(requestHandle, request.GetMethod()); + return numberOfBytesRead; } // Read the response from the sent request. @@ -1043,33 +1380,7 @@ size_t _detail::WinHttpStream::OnRead(uint8_t* buffer, size_t count, Context con return 0; } - // No need to check for context cancellation before the first I/O because the base class - // BodyStream::Read already does that. - (void)context; - - DWORD numberOfBytesRead = 0; - - if (!WinHttpReadData( - this->m_requestHandle.get(), - (LPVOID)(buffer), - static_cast(count), - &numberOfBytesRead)) - { - // Errors include: - // ERROR_WINHTTP_CONNECTION_ERROR - // ERROR_WINHTTP_INCORRECT_HANDLE_STATE - // ERROR_WINHTTP_INCORRECT_HANDLE_TYPE - // ERROR_WINHTTP_INTERNAL_ERROR - // ERROR_WINHTTP_OPERATION_CANCELLED - // ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW - // ERROR_WINHTTP_TIMEOUT - // ERROR_NOT_ENOUGH_MEMORY - - DWORD error = GetLastError(); - throw Azure::Core::Http::TransportException( - "Error while reading available data from the wire. Error Code: " + std::to_string(error) - + "."); - } + size_t numberOfBytesRead = m_requestHandle->ReadData(buffer, count, context); this->m_streamTotalRead += numberOfBytesRead; diff --git a/sdk/core/azure-core/test/ut/azure_core_test.cpp b/sdk/core/azure-core/test/ut/azure_core_test.cpp index bf312f1565..1b257efd75 100644 --- a/sdk/core/azure-core/test/ut/azure_core_test.cpp +++ b/sdk/core/azure-core/test/ut/azure_core_test.cpp @@ -3,15 +3,10 @@ #include -#if defined(BUILD_CURL_HTTP_TRANSPORT_ADAPTER) -#include -#endif +#include "azure/core/internal/diagnostics/global_exception.hpp" +#include "azure/core/platform.hpp" +#include #include -#include - -// C Runtime Library errors trigger SIGABRT. Catch that signal handler and report it on the test -// log. -void AbortHandler(int signal) { std::cout << "abort() has been called: " << signal << std::endl; } int main(int argc, char** argv) { @@ -21,7 +16,16 @@ int main(int argc, char** argv) signal(SIGPIPE, SIG_IGN); #endif - signal(SIGABRT, AbortHandler); + // Declare a signal handler to report unhandled exceptions on Windows - this is not needed for + // other OS's as they will print the exception to stderr in their terminate() function. +#if defined(AZ_PLATFORM_WINDOWS) + // Ensure that all calls to abort() no longer pop up a modal dialog on Windows. +#if defined(_DEBUG) && defined(_MSC_VER) + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); +#endif + + signal(SIGABRT, Azure::Core::Diagnostics::_internal::GlobalExceptionHandler::HandleSigAbort); +#endif // AZ_PLATFORM_WINDOWS testing::InitGoogleTest(&argc, argv); auto r = RUN_ALL_TESTS(); diff --git a/sdk/core/azure-core/test/ut/azure_libcurl_core_main_test.cpp b/sdk/core/azure-core/test/ut/azure_libcurl_core_main_test.cpp index feadd5f1c5..56d090f6c3 100644 --- a/sdk/core/azure-core/test/ut/azure_libcurl_core_main_test.cpp +++ b/sdk/core/azure-core/test/ut/azure_libcurl_core_main_test.cpp @@ -13,21 +13,21 @@ #if !defined(NOMINMAX) #define NOMINMAX #endif +#include +#include #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include +#include "azure/core/context.hpp" +#include "azure/core/http/curl_transport.hpp" +#include "azure/core/http/http.hpp" +#include "azure/core/http/policies/policy.hpp" +#include "azure/core/internal/diagnostics/global_exception.hpp" +#include "azure/core/io/body_stream.hpp" +#include "azure/core/response.hpp" +#include "http/curl/curl_connection_pool_private.hpp" +#include "http/curl/curl_connection_private.hpp" +#include "http/curl/curl_session_private.hpp" namespace Azure { namespace Core { namespace Test { TEST(SdkWithLibcurl, globalCleanUp) @@ -57,6 +57,17 @@ namespace Azure { namespace Core { namespace Test { int main(int argc, char** argv) { + // Declare a signal handler to report unhandled exceptions on Windows - this is not needed for + // other OS's as they will print the exception to stderr in their terminate() function. +#if defined(AZ_PLATFORM_WINDOWS) + // Ensure that all calls to abort() no longer pop up a modal dialog on Windows. +#if defined(_DEBUG) && defined(_MSC_VER) + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); +#endif + + signal(SIGABRT, Azure::Core::Diagnostics::_internal::GlobalExceptionHandler::HandleSigAbort); +#endif // AZ_PLATFORM_WINDOWS + testing::InitGoogleTest(&argc, argv); auto r = RUN_ALL_TESTS(); return r; diff --git a/sdk/core/azure-core/test/ut/curl_connection_pool_test.cpp b/sdk/core/azure-core/test/ut/curl_connection_pool_test.cpp index 94dc7fa1ad..6ec133b8ac 100644 --- a/sdk/core/azure-core/test/ut/curl_connection_pool_test.cpp +++ b/sdk/core/azure-core/test/ut/curl_connection_pool_test.cpp @@ -467,7 +467,7 @@ namespace Azure { namespace Core { namespace Test { { // Request with port - std::string const authority(AzureSdkHttpbinServer::WithPort()); + std::string const authority(AzureSdkHttpbinServer::GetWithPort()); Azure::Core::Http::Request req( Azure::Core::Http::HttpMethod::Get, Azure::Core::Url(authority)); std::string const expectedConnectionKey(CreateConnectionKey( @@ -543,7 +543,7 @@ namespace Azure { namespace Core { namespace Test { } { // Request with port - std::string const authority(AzureSdkHttpbinServer::WithPort()); + std::string const authority(AzureSdkHttpbinServer::GetWithPort()); Azure::Core::Http::Request req( Azure::Core::Http::HttpMethod::Get, Azure::Core::Url(authority)); std::string const expectedConnectionKey(CreateConnectionKey( diff --git a/sdk/core/azure-core/test/ut/transport_adapter_base_test.cpp b/sdk/core/azure-core/test/ut/transport_adapter_base_test.cpp index 0da2ce131e..8f34dfe6b7 100644 --- a/sdk/core/azure-core/test/ut/transport_adapter_base_test.cpp +++ b/sdk/core/azure-core/test/ut/transport_adapter_base_test.cpp @@ -311,7 +311,7 @@ namespace Azure { namespace Core { namespace Test { // Test that calling getValue again will return empty result = std::move(responseT.Value); EXPECT_STREQ(result.data(), expectedType.data()); - result = responseT.Value; + result = responseT.Value; // Not 100% sure what this is testing - that std::move works? EXPECT_STREQ(result.data(), std::string("").data()); } @@ -376,6 +376,18 @@ namespace Azure { namespace Core { namespace Test { t1.join(); } + TEST_P(TransportAdapter, cancelRequest) + { + Azure::Core::Url hostPath(AzureSdkHttpbinServer::Delay() + "/10"); // 10 seconds delay on server + Azure::Core::Context cancelThis = Azure::Core::Context::ApplicationContext.WithDeadline( + std::chrono::system_clock::now() + std::chrono::seconds(3)); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, hostPath); + + // Request will be cancelled 3 seconds after sending the request. + EXPECT_THROW(m_pipeline->Send(request, cancelThis), Azure::Core::OperationCancelledException); + } + TEST_P(TransportAdapter, cancelTransferDownload) { // public big blob (321MB) diff --git a/sdk/core/azure-core/test/ut/transport_adapter_base_test.hpp b/sdk/core/azure-core/test/ut/transport_adapter_base_test.hpp index c1aa59a781..6c357ce9fa 100644 --- a/sdk/core/azure-core/test/ut/transport_adapter_base_test.hpp +++ b/sdk/core/azure-core/test/ut/transport_adapter_base_test.hpp @@ -26,36 +26,13 @@ namespace Azure { namespace Core { namespace Test { struct AzureSdkHttpbinServer final { - inline static std::string Get() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + "/get"; - } - inline static std::string Headers() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + "/headers"; - } - inline static std::string WithPort() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + ":443/get"; - } - inline static std::string Put() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + "/put"; - } - inline static std::string Delete() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + "/delete"; - } - inline static std::string Patch() - { - return std::string(_detail::AzureSdkHttpbinServerSchema) + "://" - + std::string(_detail::AzureSdkHttpbinServer) + "/patch"; - } + inline static std::string Get() { return Schema() + "://" + Host() + "/get"; } + inline static std::string Headers() { return Schema() + "://" + Host() + "/headers"; } + inline static std::string GetWithPort() { return Schema() + "://" + Host() + ":443/get"; } + inline static std::string Put() { return Schema() + "://" + Host() + "/put"; } + inline static std::string Delete() { return Schema() + "://" + Host() + "/delete"; } + inline static std::string Patch() { return Schema() + "://" + Host() + "/patch"; } + inline static std::string Delay() { return Schema() + "://" + Host() + "/delay"; } inline static std::string Host() { return std::string(_detail::AzureSdkHttpbinServer); } inline static std::string Schema() { return std::string(_detail::AzureSdkHttpbinServerSchema); } }; diff --git a/sdk/core/azure-core/test/ut/transport_policy_options.cpp b/sdk/core/azure-core/test/ut/transport_policy_options.cpp index 1c930ca7a2..4483a65155 100644 --- a/sdk/core/azure-core/test/ut/transport_policy_options.cpp +++ b/sdk/core/azure-core/test/ut/transport_policy_options.cpp @@ -526,11 +526,14 @@ namespace Azure { namespace Core { namespace Test { TEST_F(TransportAdapterOptions, MultipleCrlOperations) { + // LetsEncrypt certificates don't contain a distribution point URL extension. While this seems + // to work when run locally, it fails in the CI pipeline. "https://www.wikipedia.org" uses a + // LetsEncrypt certificate, so when testing manually, it is important to add it to the list. std::vector testUrls{ - AzureSdkHttpbinServer::Get(), - "https://twitter.com/", - "https://www.example.com/", - "https://www.google.com/", + AzureSdkHttpbinServer::Get(), // Uses a Microsoft/DigiCert certificate. + "https://aws.amazon.com", // Uses a Amazon/Starfield Technologies certificate. + "https://www.example.com/", // Uses a DigiCert certificate. + "https://www.google.com/", // Uses a google certificate. }; GTEST_LOG_(INFO) << "Basic test calls."; diff --git a/sdk/core/perf/src/program.cpp b/sdk/core/perf/src/program.cpp index 73a68440f7..c788388c72 100644 --- a/sdk/core/perf/src/program.cpp +++ b/sdk/core/perf/src/program.cpp @@ -4,6 +4,7 @@ #include "azure/perf/program.hpp" #include "azure/perf/argagg.hpp" +#include #include #include #include @@ -286,19 +287,10 @@ void Azure::Perf::Program::Run( _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); #endif -// Declare a signal handler to report unhandled exceptions on Windows - this is not needed for other -// OS's as they will print the exception to stderr in their terminate() function. + // Declare a signal handler to report unhandled exceptions on Windows - this is not needed for + // other OS's as they will print the exception to stderr in their terminate() function. #if defined(AZ_PLATFORM_WINDOWS) - signal(SIGABRT, [](int) { - try - { - throw; - } - catch (std::exception const& ex) - { - std::cout << "Exception thrown: " << ex.what() << std::endl; - } - }); + signal(SIGABRT, Azure::Core::Diagnostics::_internal::GlobalExceptionHandler::HandleSigAbort); #endif // AZ_PLATFORM_WINDOWS // Parse args only to get the test name first