diff --git a/sdk/core/azure-core/CMakeLists.txt b/sdk/core/azure-core/CMakeLists.txt index 3373bb12d..7e003a8d6 100644 --- a/sdk/core/azure-core/CMakeLists.txt +++ b/sdk/core/azure-core/CMakeLists.txt @@ -8,11 +8,14 @@ project(${TARGET_NAME} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED True) -set(CURL_MIN_REQUIRED_VERSION 7.4) +# min version for `CURLSSLOPT_NO_REVOKE` +# https://curl.haxx.se/libcurl/c/CURLOPT_SSL_OPTIONS.html +set(CURL_MIN_REQUIRED_VERSION 7.44) find_package(CURL ${CURL_MIN_REQUIRED_VERSION} CONFIG) if(NOT CURL_FOUND) find_package(CURL ${CURL_MIN_REQUIRED_VERSION} REQUIRED) endif() +message("Libcurl version ${CURL_VERSION_STRING}") if(BUILD_TRANSPORT_CURL) SET(CURL_TRANSPORT_ADAPTER_SRC src/http/curl/curl.cpp) diff --git a/sdk/core/azure-core/inc/azure/core/http/curl/curl connection_pool.hpp b/sdk/core/azure-core/inc/azure/core/http/curl/curl connection_pool.hpp index 5cc115c29..8948b2bb1 100644 --- a/sdk/core/azure-core/inc/azure/core/http/curl/curl connection_pool.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/curl/curl connection_pool.hpp @@ -27,6 +27,84 @@ namespace Azure { namespace Core { namespace Test { namespace Azure { namespace Core { namespace Http { + /** + * @brief The available options to set libcurl ssl options. + * + * @remark The SDK will map the enum option to libcurl's specific option. See more info here: + * https://curl.haxx.se/libcurl/c/CURLOPT_SSL_OPTIONS.html + * + */ + struct CurlTransportSSLOptions + { + bool AllowBeast = false; + bool NoRevoke = false; + /* + // Requires libcurl version >= 7.68 + bool NoPartialchain = false; + // Requires libcurl version >= 7.70 + bool RevokeBestEffort = false; + // Requires libcurl version >= 7.71 + bool NativeCa = false; + */ + }; + + /** + * @brief Set the curl connection options like a proxy and CA path. + * + */ + struct CurlTransportOptions + { + /** + * @brief The string for the proxy is passed directly to the libcurl handle without any parsing + * + * @remark No validation for the string is done by the Azure SDK. More about this option: + * https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html. + * + * @remark The default value is an empty string (no proxy). + * + */ + std::string Proxy; + /** + * @brief The string for the certificate authenticator is sent to libcurl handle directly. + * + * @remark The Azure SDK will not check if the path is valid or not. + * + * @remark The default is the built-in system specific path. More about this option: + * https://curl.haxx.se/libcurl/c/CURLOPT_CAINFO.html + * + */ + std::string CAInfo; + /** + * @brief All HTTP requests will keep the connection channel open to the service. + * + * @remark The channel might be closed by the server if the server response has an error code. + * A connection won't be re-used if it is abandoned in the middle of an operation. + * operation. + * + * @remark This option is managed directly by the Azure SDK. No option is set for the curl + * handle. It is `true` by default. + */ + bool HttpKeepAlive = true; + /** + * @brief This option determines whether curl verifies the authenticity of the peer's + * certificate. + * + * @remark The default value is `true`. More about this option: + * https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html + * + */ + bool SSLVerifyPeer = true; + + /** + * @brief Define the SSL options for the libcurl handle. + * + * @remark See more info here: https://curl.haxx.se/libcurl/c/CURLOPT_SSL_OPTIONS.html. + * The default option is all options `false`. + * + */ + CurlTransportSSLOptions SSLOptions; + }; + /** * @brief CURL HTTP connection pool makes it possible to re-use one curl connection to perform * more than one request. Use this component when connections are not re-used by default. @@ -64,7 +142,9 @@ namespace Azure { namespace Core { namespace Http { * * @return #CurlNetworkConnection to use. */ - static std::unique_ptr GetCurlConnection(Request& request); + static std::unique_ptr GetCurlConnection( + Request& request, + CurlTransportOptions const& options); /** * @brief Moves a connection back to the pool to be re-used. diff --git a/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp b/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp index 933502756..1515e9039 100644 --- a/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp @@ -18,13 +18,24 @@ #include "azure/core/http/policy.hpp" namespace Azure { namespace Core { namespace Http { - /** * @brief Concrete implementation of an HTTP Transport that uses libcurl. * */ class CurlTransport : public HttpTransport { + private: + CurlTransportOptions m_options; + public: + /** + * @brief Construct a new Curl Transport object. + * + * @param options Optional parameter to override the default options. + */ + CurlTransport(CurlTransportOptions const& options = CurlTransportOptions()) : m_options(options) + { + } + /** * @brief Implements interface to send an HTTP Request and produce an HTTP RawResponse * diff --git a/sdk/core/azure-core/inc/azure/core/http/curl/curl_session.hpp b/sdk/core/azure-core/inc/azure/core/http/curl/curl_session.hpp index 666bc0543..39fc2da66 100644 --- a/sdk/core/azure-core/inc/azure/core/http/curl/curl_session.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/curl/curl_session.hpp @@ -319,14 +319,25 @@ namespace Azure { namespace Core { namespace Http { return eof && m_sessionState != SessionState::PERFORM; } + /** + * @brief All connections will request to keep the channel open to re-use the + * connection. + * + * @remark This option can be disabled from the transport adapter options. When disabled, the + * session won't return connections to the connection pool. Connection will be closed as soon as + * the request is completed. + * + */ + bool m_keepAlive = true; + public: /** * @brief Construct a new Curl Session object. Init internal libcurl handler. * * @param request reference to an HTTP Request. */ - CurlSession(Request& request, std::unique_ptr connection) - : m_connection(std::move(connection)), m_request(request) + CurlSession(Request& request, std::unique_ptr connection, bool keepAlive) + : m_connection(std::move(connection)), m_request(request), m_keepAlive(keepAlive) { m_bodyStartInBuffer = -1; m_innerBufferSize = Details::c_DefaultLibcurlReaderSize; @@ -342,7 +353,7 @@ namespace Azure { namespace Core { namespace Http { // By not moving the connection back to the pool, it gets destroyed calling the connection // destructor to clean libcurl handle and close the connection. // IsEOF will also handle a connection that fail to complete an upload request. - if (IsEOF()) + if (IsEOF() && m_keepAlive) { CurlConnectionPool::MoveConnectionBackToPool(std::move(m_connection), m_lastStatusCode); } diff --git a/sdk/core/azure-core/src/http/curl/curl.cpp b/sdk/core/azure-core/src/http/curl/curl.cpp index 9b78dced2..35b257ae9 100644 --- a/sdk/core/azure-core/src/http/curl/curl.cpp +++ b/sdk/core/azure-core/src/http/curl/curl.cpp @@ -13,6 +13,7 @@ #endif #include +#include #include #include @@ -145,6 +146,7 @@ using Azure::Core::Http::CurlConnectionPool; using Azure::Core::Http::CurlNetworkConnection; using Azure::Core::Http::CurlSession; using Azure::Core::Http::CurlTransport; +using Azure::Core::Http::CurlTransportOptions; using Azure::Core::Http::HttpStatusCode; using Azure::Core::Http::LogClassification; using Azure::Core::Http::RawResponse; @@ -155,8 +157,8 @@ std::unique_ptr CurlTransport::Send(Context const& context, Request { // Create CurlSession to perform request LogThis("Creating a new session."); - auto session - = std::make_unique(request, CurlConnectionPool::GetCurlConnection(request)); + auto session = std::make_unique( + request, CurlConnectionPool::GetCurlConnection(request, m_options), m_options.HttpKeepAlive); CURLcode performing; // Try to send the request. If we get CURLE_UNSUPPORTED_PROTOCOL back, it means the connection is @@ -173,8 +175,10 @@ std::unique_ptr CurlTransport::Send(Context const& context, Request break; } // Let session be destroyed and create a new one to get a new connection - session - = std::make_unique(request, CurlConnectionPool::GetCurlConnection(request)); + session = std::make_unique( + request, + CurlConnectionPool::GetCurlConnection(request, m_options), + m_options.HttpKeepAlive); } if (performing != CURLE_OK) @@ -1012,7 +1016,9 @@ std::map>> int32_t CurlConnectionPool::s_connectionCounter = 0; bool CurlConnectionPool::s_isCleanConnectionsRunning = false; -std::unique_ptr CurlConnectionPool::GetCurlConnection(Request& request) +std::unique_ptr CurlConnectionPool::GetCurlConnection( + Request& request, + CurlTransportOptions const& options) { std::string const& host = request.GetUrl().GetHost(); @@ -1073,6 +1079,73 @@ std::unique_ptr CurlConnectionPool::GetCurlConnection(Req 60L * 60L * 24L, Details::c_DefaultFailedToGetNewConnectionTemplate + host); + /******************** Curl handle options apply to all connections created + * The keepAlive option is managed by the session directly. + */ + if (!options.Proxy.empty()) + { + SetLibcurlOption( + newHandle, + CURLOPT_PROXY, + options.Proxy.c_str(), + Details::c_DefaultFailedToGetNewConnectionTemplate + host + + ". Failed to set proxy to:" + options.Proxy); + } + + if (!options.CAInfo.empty()) + { + SetLibcurlOption( + newHandle, + CURLOPT_CAINFO, + options.CAInfo.c_str(), + Details::c_DefaultFailedToGetNewConnectionTemplate + host + + ". Failed to set CA cert to:" + options.CAInfo); + } + + long sslOption = 0; + if (options.SSLOptions.AllowBeast) + { + sslOption |= CURLSSLOPT_ALLOW_BEAST; + } + if (options.SSLOptions.NoRevoke) + { + sslOption |= CURLSSLOPT_NO_REVOKE; + } + /* + // Requires libcurl version >= 7.68 + if (options.SSLOptions.NoPartialchain) + { + sslOption |= CURLSSLOPT_NO_PARTIALCHAIN; + } + // Requires libcurl version >= 7.70 + if (options.SSLOptions.RevokeBestEffort) + { + sslOption |= CURLSSLOPT_REVOKE_BEST_EFFORT; + } + // Requires libcurl version >= 7.71 + if (options.SSLOptions.NativeCa) + { + sslOption |= CURLSSLOPT_NATIVE_CA; + } + */ + + SetLibcurlOption( + newHandle, + CURLOPT_SSL_OPTIONS, + sslOption, + Details::c_DefaultFailedToGetNewConnectionTemplate + host + + ". Failed to set ssl options to long bitmask:" + std::to_string(sslOption)); + + if (!options.SSLVerifyPeer) + { + SetLibcurlOption( + newHandle, + CURLOPT_SSL_VERIFYPEER, + 0L, + Details::c_DefaultFailedToGetNewConnectionTemplate + host + + ". Failed to disable ssl verify peer."); + } + auto performResult = curl_easy_perform(newHandle); if (performResult != CURLE_OK) { diff --git a/sdk/core/azure-core/test/ut/CMakeLists.txt b/sdk/core/azure-core/test/ut/CMakeLists.txt index fa8c773d5..5054fcb9f 100644 --- a/sdk/core/azure-core/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core/test/ut/CMakeLists.txt @@ -19,10 +19,15 @@ project (${TARGET_NAME} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED True) +if(BUILD_TRANSPORT_CURL) + SET(CURL_OPTIONS_TESTS curl_options.cpp) +endif() + include(GoogleTest) add_executable ( ${TARGET_NAME} context.cpp + ${CURL_OPTIONS_TESTS} curl_session_test.cpp datetime.cpp http.cpp diff --git a/sdk/core/azure-core/test/ut/curl_options.cpp b/sdk/core/azure-core/test/ut/curl_options.cpp new file mode 100644 index 000000000..58d0df506 --- /dev/null +++ b/sdk/core/azure-core/test/ut/curl_options.cpp @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Azure { namespace Core { namespace Test { + + // proxy server can take some minutes to handle the request. Only testing HTTP proxy + // Test is disabled until there is a reliable proxy to be used for CI111 + TEST(CurlTransportOptions, DISABLED_proxy) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + // This proxy is currently alive but eventually we might want our own proxy server to be + // available. + curlOptions.Proxy = "136.228.165.138:8080"; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("http://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + } + + /******************************* SSL options. ************************/ + TEST(CurlTransportOptions, noRevoke) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.SSLOptions.NoRevoke = true; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + + // Clean the connection from the pool *Windows fails to clean if we leave to be clean uppon + // app-destruction + EXPECT_NO_THROW(Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.clear()); + } + + TEST(CurlTransportOptions, allowBeast) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.SSLOptions.AllowBeast = true; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + + // Clean the connection from the pool *Windows fails to clean if we leave to be clean uppon + // app-destruction + EXPECT_NO_THROW(Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.clear()); + } + + /* + // Requires libcurl version >= 7.68 + TEST(CurlTransportOptions, nativeCA) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.SSLOptions.NativeCa = true; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + } + + // Requires libcurl version >= 7.70 + TEST(CurlTransportOptions, noPartialChain) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.SSLOptions.NoPartialchain = true; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + } + + // Requires libcurl version >= 7.71 + TEST(CurlTransportOptions, bestEffort) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.SSLOptions.RevokeBestEffort = true; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + } + */ + + TEST(CurlTransportOptions, sslVerifyOff) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + // If ssl verify is not disabled, this test would fail because the caInfo is not OK + curlOptions.SSLVerifyPeer = false; + // This ca info should be ignored by verify disable and test should work + curlOptions.CAInfo = "/"; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + // Use https + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + + // Clean the connection from the pool *Windows fails to clean if we leave to be clean uppon + // app-destruction + EXPECT_NO_THROW(Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.clear()); + } + + TEST(CurlTransportOptions, httpsDefault) + { + auto transportAdapter = std::make_shared(); + auto transportPolicy = std::make_unique(transportAdapter); + + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + // Use https + Azure::Core::Http::Url url("https://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + + // Clean the connection from the pool *Windows fails to clean if we leave to be clean uppon + // app-destruction + EXPECT_NO_THROW(Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.clear()); + } + + TEST(CurlTransportOptions, disableKeepAlive) + { + Azure::Core::Http::CurlTransportOptions curlOptions; + curlOptions.HttpKeepAlive = false; + + auto transportAdapter = std::make_shared(curlOptions); + auto transportPolicy = std::make_unique(transportAdapter); + + { + // use inner scope to remove the pipeline and make sure we don't keep the connection in the + // pool + std::vector> policies; + policies.emplace_back(std::move(transportPolicy)); + Azure::Core::Http::HttpPipeline pipeline(policies); + + Azure::Core::Http::Url url("http://httpbin.org/get"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + std::unique_ptr response; + EXPECT_NO_THROW(response = pipeline.Send(Azure::Core::GetApplicationContext(), request)); + auto responseCode = response->GetStatusCode(); + int expectedCode = 200; + EXPECT_PRED2( + [](int expected, int code) { return expected == code; }, + expectedCode, + static_cast::type>( + responseCode)); + } + // Make sure there are no connections in the pool + EXPECT_EQ(Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.size(), 0); + } + +}}} // namespace Azure::Core::Test diff --git a/sdk/core/azure-core/test/ut/curl_session_test.cpp b/sdk/core/azure-core/test/ut/curl_session_test.cpp index 46e218f4b..b3b33559f 100644 --- a/sdk/core/azure-core/test/ut/curl_session_test.cpp +++ b/sdk/core/azure-core/test/ut/curl_session_test.cpp @@ -37,8 +37,8 @@ namespace Azure { namespace Core { namespace Test { // Move the curlMock to build a session and then send the request // The session will get the response we mock before, so it will pass for this GET - auto session - = std::make_unique(request, std::move(uniqueCurlMock)); + auto session = std::make_unique( + request, std::move(uniqueCurlMock), true); EXPECT_NO_THROW(session->Perform(Azure::Core::GetApplicationContext())); } @@ -70,8 +70,8 @@ namespace Azure { namespace Core { namespace Test { { // Create the session inside scope so it is released and the connection is moved to the pool - auto session - = std::make_unique(request, std::move(uniqueCurlMock)); + auto session = std::make_unique( + request, std::move(uniqueCurlMock), true); EXPECT_NO_THROW(session->Perform(Azure::Core::GetApplicationContext())); } diff --git a/sdk/core/azure-core/test/ut/transport_adapter.cpp b/sdk/core/azure-core/test/ut/transport_adapter.cpp index 8d218e5a5..90b7fade7 100644 --- a/sdk/core/azure-core/test/ut/transport_adapter.cpp +++ b/sdk/core/azure-core/test/ut/transport_adapter.cpp @@ -480,31 +480,31 @@ namespace Azure { namespace Core { namespace Test { t1.join(); } - TEST_F(TransportAdapter, requestFailedException) - { - Azure::Core::Http::Url host("http://unresolvedHost.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - EXPECT_THROW(pipeline.Send(context, request), Azure::Core::RequestFailedException); - } - - TEST_F(TransportAdapter, dynamicCast) - { - Azure::Core::Http::Url host("http://unresolvedHost.org/get"); - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - - // test dynamic cast - try + TEST_F(TransportAdapter, requestFailedException) { - auto result = pipeline.Send(context, request); + Azure::Core::Http::Url host("http://unresolvedHost.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + EXPECT_THROW(pipeline.Send(context, request), Azure::Core::RequestFailedException); } - catch (Azure::Core::RequestFailedException& err) + + TEST_F(TransportAdapter, dynamicCast) { - // if ref can't be cast, it throws - EXPECT_NO_THROW(dynamic_cast(err)); - EXPECT_NO_THROW(dynamic_cast(err)); - EXPECT_THROW(dynamic_cast(err), std::bad_cast); + Azure::Core::Http::Url host("http://unresolvedHost.org/get"); + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + + // test dynamic cast + try + { + auto result = pipeline.Send(context, request); + } + catch (Azure::Core::RequestFailedException& err) + { + // if ref can't be cast, it throws + EXPECT_NO_THROW(dynamic_cast(err)); + EXPECT_NO_THROW(dynamic_cast(err)); + EXPECT_THROW(dynamic_cast(err), std::bad_cast); + } } - } }}} // namespace Azure::Core::Test