Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion sdk/core/azure-core/inc/azure/core/http/curl_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/http/transport.hpp"
#include "azure/core/nullable.hpp"

#include <functional>
#include <chrono>
#include <memory>
#include <string>
Expand Down Expand Up @@ -199,6 +199,15 @@ namespace Azure { namespace Core { namespace Http {
* @brief If set, enables libcurl's internal SSL session caching.
*/
bool EnableCurlSslCaching = true;

/**
* @brief Optional callback to customize CURL handle before request execution.
* @details Allows setting additional CURL options per request, such as CURLOPT_INTERFACE
* for network interface binding. The callback receives the CURL* handle (as void*) and can
* call curl_easy_setopt() directly to configure request-specific options.
* @remark This callback is invoked just before curl_easy_perform() is called.
*/
std::function<void(void*)> CurlOptionsCallback;
};

/**
Expand Down Expand Up @@ -253,3 +262,4 @@ namespace Azure { namespace Core { namespace Http {
};

}}} // namespace Azure::Core::Http

13 changes: 10 additions & 3 deletions sdk/core/azure-core/src/http/curl/curl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,13 @@ CurlConnection::CurlConnection(
}
}

// Apply custom CURL options callback BEFORE setting URL
// This allows the callback to set options like CURLOPT_INTERFACE before CURL processes the URL
if (options.CurlOptionsCallback)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have an options callback, you need to either:

  1. Apply the option to every curl connection created equally
  2. Exempt the connection from the connection pool or
  3. Come up with a way to distinguish between curl connection options in the connection pool.

Otherwise, the connection pool will re-use a custom configured curl handle in an environment that doesn't expect that configuration.

{
options.CurlOptionsCallback(static_cast<void*>(m_handle.get()));
}

// Libcurl setup before open connection (url, connect_only, timeout)
if (!SetLibcurlOption(m_handle, CURLOPT_URL, request.GetUrl().GetAbsoluteUrl().data(), &result))
{
Expand Down Expand Up @@ -2637,11 +2644,11 @@ CurlConnection::CurlConnection(
throw Azure::Core::Http::TransportException(
_detail::DefaultFailedToGetNewConnectionTemplate + hostDisplayName
+ ". Failed enforcing TLS v1.2 or greater. " + std::string(curl_easy_strerror(result)));
}

}
auto performResult = curl_easy_perform(m_handle.get());

if (performResult != CURLE_OK)
{
{
#if defined(AZ_PLATFORM_LINUX)
if (performResult == CURLE_PEER_FAILED_VERIFICATION)
{
Expand Down
58 changes: 58 additions & 0 deletions sdk/core/azure-core/test/ut/curl_options_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,62 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_NO_THROW(Azure::Core::Http::_detail::CurlConnectionPool::g_curlConnectionPool
.ConnectionPoolIndex.clear());
}

// Test CurlOptionsCallback functionality
TEST(CurlTransportOptions, CurlOptionsCallback)
{
Azure::Core::Http::CurlTransportOptions curlOptions;

// Track if callback was invoked
bool callbackInvoked = false;

// Set up callback to customize CURL handle
curlOptions.CurlOptionsCallback = [&callbackInvoked](void* curlHandle) {
callbackInvoked = true;

// Example: Set a custom timeout using the callback
// Cast void* back to CURL* to use curl_easy_setopt
CURL* handle = static_cast<CURL*>(curlHandle);

// Set a custom option - for example, verbose mode for debugging
curl_easy_setopt(handle, CURLOPT_VERBOSE, 0L);

// You could set CURLOPT_INTERFACE here for network interface binding:
// curl_easy_setopt(handle, CURLOPT_INTERFACE, "eth0");
};

auto transportAdapter = std::make_shared<Azure::Core::Http::CurlTransport>(curlOptions);
Azure::Core::Http::Policies::TransportOptions options;
options.Transport = transportAdapter;
auto transportPolicy
= std::make_unique<Azure::Core::Http::Policies::_internal::TransportPolicy>(options);

std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
policies.emplace_back(std::move(transportPolicy));
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);

Azure::Core::Url url(AzureSdkHttpbinServer::Get());
Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url);

std::unique_ptr<Azure::Core::Http::RawResponse> response;
EXPECT_NO_THROW(response = pipeline.Send(request, Azure::Core::Context{}));

// Verify callback was invoked
EXPECT_TRUE(callbackInvoked);

if (response)
{
auto responseCode = response->GetStatusCode();
int expectedCode = 200;
EXPECT_PRED2(
[](int expected, int code) { return expected == code; },
expectedCode,
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
responseCode));
}

// Clean the connection from the pool
EXPECT_NO_THROW(Azure::Core::Http::_detail::CurlConnectionPool::g_curlConnectionPool
.ConnectionPoolIndex.clear());
}
}}} // namespace Azure::Core::Test
Loading