diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index c62c4c789c..2e354d1b10 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -3,12 +3,15 @@ ## 1.2.0-beta.1 (Unreleased) ### Features Added -- Added `Azure::Core::IO::ProgressBodyStream` type that wraps an existing BodyStream based type stream and reports progress via callback when the stream position is updated. + +- Added `Azure::Core::IO::ProgressBodyStream` type that wraps an existing BodyStream based type stream and reports progress via callback when the stream position is updated. ### Breaking Changes ### Bugs Fixed +- [2647](https://github.com/Azure/azure-sdk-for-cpp/issues/2647) Make the curl transport adapter to check the connection close header. + ### Other Changes ## 1.1.0 (2021-07-02) diff --git a/sdk/core/azure-core/src/http/curl/curl.cpp b/sdk/core/azure-core/src/http/curl/curl.cpp index 6b84ef6807..b48026f2e3 100644 --- a/sdk/core/azure-core/src/http/curl/curl.cpp +++ b/sdk/core/azure-core/src/http/curl/curl.cpp @@ -714,7 +714,20 @@ void CurlSession::ReadStatusLineAndHeadersFromRawResponse( } // headers are already lowerCase at this point - auto headers = this->m_response->GetHeaders(); + auto const& headers = this->m_response->GetHeaders(); + + // Check if server has return the connection header. This header can be used to stop re-using the + // connection. The `Iot Edge Blob Storage Module` is known to return this after some time re-using + // the same http secured channel. + auto connectionHeader = headers.find("connection"); + if (connectionHeader != headers.end()) + { + if (connectionHeader->second == "close") + { + // Use connection shut-down so it won't be moved it back to the connection pool. + m_connection->Shutdown(); + } + } auto isContentLengthHeaderInResponse = headers.find("content-length"); if (isContentLengthHeaderInResponse != headers.end()) diff --git a/sdk/core/azure-core/src/http/curl/curl_connection_pool_private.hpp b/sdk/core/azure-core/src/http/curl/curl_connection_pool_private.hpp index 1beb4c0b00..c6e0aeb759 100644 --- a/sdk/core/azure-core/src/http/curl/curl_connection_pool_private.hpp +++ b/sdk/core/azure-core/src/http/curl/curl_connection_pool_private.hpp @@ -29,6 +29,7 @@ namespace Azure { namespace Core { namespace Test { class CurlConnectionPool_connectionPoolTest_Test; class CurlConnectionPool_uniquePort_Test; + class CurlConnectionPool_connectionClose_Test; }}} // namespace Azure::Core::Test #endif @@ -46,6 +47,7 @@ namespace Azure { namespace Core { namespace Http { namespace _detail { // Give access to private to this tests class friend class Azure::Core::Test::CurlConnectionPool_connectionPoolTest_Test; friend class Azure::Core::Test::CurlConnectionPool_uniquePort_Test; + friend class Azure::Core::Test::CurlConnectionPool_connectionClose_Test; #endif public: 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 028288df0f..15eb6c3ad2 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 @@ -554,5 +554,45 @@ namespace Azure { namespace Core { namespace Test { } } + TEST(CurlConnectionPool, connectionClose) + { + /// When getting the header connection: close from an HTTP response, the connection should not + /// be moved back to the pool. + { + std::lock_guard lock( + CurlConnectionPool::g_curlConnectionPool.ConnectionPoolMutex); + CurlConnectionPool::g_curlConnectionPool.ConnectionPoolIndex.clear(); + // Make sure there are nothing in the pool + EXPECT_EQ(CurlConnectionPool::g_curlConnectionPool.ConnectionPoolIndex.size(), 0); + } + + // Use the same request for all connections. + Azure::Core::Http::Request req( + Azure::Core::Http::HttpMethod::Get, Azure::Core::Url(AzureSdkHttpbinServer::Headers())); + // Server will return this header back + req.SetHeader("connection", "close"); + + { + // Create a pipeline to send the request and dispose after it. + Azure::Core::Http::_internal::HttpPipeline pipeline({}, "test", "test", {}, {}); + auto response = pipeline.Send(req, Azure::Core::Context::ApplicationContext); + EXPECT_PRED2( + [](Azure::Core::Http::HttpStatusCode a, Azure::Core::Http::HttpStatusCode b) { + return a == b; + }, + response->GetStatusCode(), + Azure::Core::Http::HttpStatusCode::Ok); + } + + // Check that after the connection is gone, it is moved back to the pool + { + std::lock_guard lock( + CurlConnectionPool::g_curlConnectionPool.ConnectionPoolMutex); + EXPECT_EQ( + Azure::Core::Http::_detail::CurlConnectionPool::g_curlConnectionPool.ConnectionPoolIndex + .size(), + 0); + } + } #endif }}} // namespace Azure::Core::Test 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 f8a8a7b0b0..0dd22ab745 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 @@ -31,6 +31,11 @@ namespace Azure { namespace Core { namespace Test { 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) + "://"