Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\Common\Win\utils_win.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlDynamicLoader.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlEasyRequest.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlMulti.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlProvider.cpp" />
Expand All @@ -25,6 +26,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\Common\Win\utils_win.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlDynamicLoader.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlEasyRequest.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlMulti.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlProvider.h" />
Expand Down
1 change: 1 addition & 0 deletions Build/libHttpClient.GDK.props
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<SupportJustMyCode>false</SupportJustMyCode>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>__WRL_NO_DEFAULT_LIB__;_LIB;$(libHttpClientDefine);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(GameDKLatest)GRDK\ExtensionLibraries\Xbox.XCurl.API\Include</AdditionalIncludeDirectories>
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
Expand Down
2 changes: 1 addition & 1 deletion Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup Label="Globals">
<ProjectGuid>{A5A6E02A-21BA-4D55-9FB9-7B24DEDD3743}</ProjectGuid>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<GDKExtLibNames>Xbox.XCurl.API</GDKExtLibNames>
<GDKExtLibNames>;</GDKExtLibNames>
<PlatformToolset Condition="$(VisualStudioVersion)==14">v140</PlatformToolset>
<PlatformToolset Condition="$(VisualStudioVersion)==15">v141</PlatformToolset>
<PlatformToolset Condition="$(VisualStudioVersion)==16">v142</PlatformToolset>
Expand Down
1 change: 1 addition & 0 deletions Include/httpClient/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ typedef struct _LIST_ENTRY {
#define E_HC_NETWORK_NOT_INITIALIZED MAKE_E_HC(0x5007) // 0x89235007
#define E_HC_INTERNAL_STILLINUSE MAKE_E_HC(0x5008) // 0x89235008
#define E_HC_COMPRESSION_ENABLED MAKE_E_HC(0x5009) // 0x89235009
#define E_HC_XCURL_REQUIRED MAKE_E_HC(0x500A) // 0x8923500A

typedef uint32_t HCMemoryType;
typedef struct HC_WEBSOCKET_OBSERVER* HCWebsocketHandle;
Expand Down
142 changes: 142 additions & 0 deletions Source/HTTP/Curl/CurlDynamicLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include "pch.h"
#include "CurlDynamicLoader.h"

#if HC_PLATFORM == HC_PLATFORM_GDK

#include <memory>
#include <mutex>

namespace xbox
{
namespace httpclient
{

std::mutex CurlDynamicLoader::s_initMutex;
HC_UNIQUE_PTR<CurlDynamicLoader> CurlDynamicLoader::s_instance;

CurlDynamicLoader& CurlDynamicLoader::GetInstance()
{
std::lock_guard<std::mutex> lock(s_initMutex);
if (!s_instance)
{
HC_TRACE_VERBOSE(HTTPCLIENT, "Creating CurlDynamicLoader instance");

// Use libHttpClient custom allocator hooks while staying within class access to private ctor
http_stl_allocator<CurlDynamicLoader> a{};
s_instance = HC_UNIQUE_PTR<CurlDynamicLoader>{ new (a.allocate(1)) CurlDynamicLoader };
}
return *s_instance;
}

void CurlDynamicLoader::DestroyInstance()
{
std::lock_guard<std::mutex> lock(s_initMutex);
if (s_instance)
{
// Unique ptr with http_alloc_deleter ensures custom free hooks are used
s_instance.reset();
}
}

CurlDynamicLoader::~CurlDynamicLoader()
{
Cleanup();
}

bool CurlDynamicLoader::Initialize()
{
if (m_curlLibrary != nullptr)
{
HC_TRACE_VERBOSE(HTTPCLIENT, "XCurl.dll already loaded");
return true; // Already loaded
}

HC_TRACE_INFORMATION(HTTPCLIENT, "Attempting to load XCurl.dll");

// Try to load XCurl.dll
m_curlLibrary = LoadLibraryA("XCurl.dll");
if (m_curlLibrary == nullptr)
{
DWORD error = GetLastError();
HC_TRACE_ERROR(HTTPCLIENT, "Failed to load XCurl.dll. Error code: %lu", error);
return false;
}

// Load all required functions
bool success = true;

success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_global_init_fn), "curl_global_init");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_global_cleanup_fn), "curl_global_cleanup");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_init_fn), "curl_easy_init");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_cleanup_fn), "curl_easy_cleanup");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_setopt_fn), "curl_easy_setopt");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_getinfo_fn), "curl_easy_getinfo");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_strerror_fn), "curl_easy_strerror");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_slist_append_fn), "curl_slist_append");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_slist_free_all_fn), "curl_slist_free_all");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_init_fn), "curl_multi_init");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_cleanup_fn), "curl_multi_cleanup");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_add_handle_fn), "curl_multi_add_handle");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_remove_handle_fn), "curl_multi_remove_handle");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_perform_fn), "curl_multi_perform");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_info_read_fn), "curl_multi_info_read");

// Note: curl_multi_poll might not be available in older versions, so we make it optional
LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_poll_fn), "curl_multi_poll");
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_wait_fn), "curl_multi_wait");

if (!success)
{
Cleanup();
return false;
}

HC_TRACE_INFORMATION(HTTPCLIENT, "XCurl.dll loaded successfully");
return true;
}

void CurlDynamicLoader::Cleanup()
{
if (m_curlLibrary != nullptr)
{
HC_TRACE_INFORMATION(HTTPCLIENT, "Unloading XCurl.dll");
FreeLibrary(m_curlLibrary);
m_curlLibrary = nullptr;
}

// Reset all function pointers
curl_global_init_fn = nullptr;
curl_global_cleanup_fn = nullptr;
curl_easy_init_fn = nullptr;
curl_easy_cleanup_fn = nullptr;
curl_easy_setopt_fn = nullptr;
curl_easy_getinfo_fn = nullptr;
curl_easy_strerror_fn = nullptr;
curl_slist_append_fn = nullptr;
curl_slist_free_all_fn = nullptr;
curl_multi_init_fn = nullptr;
curl_multi_cleanup_fn = nullptr;
curl_multi_add_handle_fn = nullptr;
curl_multi_remove_handle_fn = nullptr;
curl_multi_perform_fn = nullptr;
curl_multi_info_read_fn = nullptr;
curl_multi_poll_fn = nullptr;
curl_multi_wait_fn = nullptr;
}

bool CurlDynamicLoader::LoadFunction(FARPROC& funcPtr, const char* functionName)
{
funcPtr = GetProcAddress(m_curlLibrary, functionName);
if (funcPtr == nullptr)
{
DWORD error = GetLastError();
HC_TRACE_ERROR(HTTPCLIENT, "Failed to load function: %s. Error code: %lu", functionName, error);
return false;
}
return true;
}

} // httpclient
} // xbox

#endif // HC_PLATFORM == HC_PLATFORM_GDK
110 changes: 110 additions & 0 deletions Source/HTTP/Curl/CurlDynamicLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#pragma once

//
// This header is always includable across platforms. On non-GDK platforms,
// the macros are defined as direct calls and the dynamic loader class is absent.
// On GDK, the dynamic loader class is available and macros route through it.
//

#if HC_PLATFORM == HC_PLATFORM_GDK

#include <windows.h>
#include <memory>
#include <mutex>
#include <XCurl.h>

namespace xbox
{
namespace httpclient
{

// Dynamic curl function pointers
class CurlDynamicLoader
{
public:
// Initialization/Cleanup functions
using curl_global_init_ptr = CURLcode(*)(long flags);
using curl_global_cleanup_ptr = void(*)();

// Easy interface functions
using curl_easy_init_ptr = CURL*(*)();
using curl_easy_cleanup_ptr = void(*)(CURL* curl);
using curl_easy_setopt_ptr = CURLcode(*)(CURL* curl, CURLoption option, ...);
using curl_easy_getinfo_ptr = CURLcode(*)(CURL* curl, CURLINFO info, ...);
using curl_easy_strerror_ptr = const char*(*)(CURLcode code);

// String list functions
using curl_slist_append_ptr = struct curl_slist*(*)(struct curl_slist* list, const char* string);
using curl_slist_free_all_ptr = void(*)(struct curl_slist* list);

// Multi interface functions
using curl_multi_init_ptr = CURLM*(*)();
using curl_multi_cleanup_ptr = CURLMcode(*)(CURLM* multi_handle);
using curl_multi_add_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle);
using curl_multi_remove_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle);
using curl_multi_perform_ptr = CURLMcode(*)(CURLM* multi_handle, int* running_handles);
using curl_multi_info_read_ptr = CURLMsg*(*)(CURLM* multi_handle, int* msgs_in_queue);
using curl_multi_poll_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* ret);
using curl_multi_wait_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* numfds);

// Function pointers
curl_global_init_ptr curl_global_init_fn = nullptr;
curl_global_cleanup_ptr curl_global_cleanup_fn = nullptr;
curl_easy_init_ptr curl_easy_init_fn = nullptr;
curl_easy_cleanup_ptr curl_easy_cleanup_fn = nullptr;
curl_easy_setopt_ptr curl_easy_setopt_fn = nullptr;
curl_easy_getinfo_ptr curl_easy_getinfo_fn = nullptr;
curl_easy_strerror_ptr curl_easy_strerror_fn = nullptr;
curl_slist_append_ptr curl_slist_append_fn = nullptr;
curl_slist_free_all_ptr curl_slist_free_all_fn = nullptr;
curl_multi_init_ptr curl_multi_init_fn = nullptr;
curl_multi_cleanup_ptr curl_multi_cleanup_fn = nullptr;
curl_multi_add_handle_ptr curl_multi_add_handle_fn = nullptr;
curl_multi_remove_handle_ptr curl_multi_remove_handle_fn = nullptr;
curl_multi_perform_ptr curl_multi_perform_fn = nullptr;
curl_multi_info_read_ptr curl_multi_info_read_fn = nullptr;
curl_multi_poll_ptr curl_multi_poll_fn = nullptr;
curl_multi_wait_ptr curl_multi_wait_fn = nullptr;

static CurlDynamicLoader& GetInstance();
// Frees the singleton instance and unloads XCurl.dll (via destructor -> Cleanup)
static void DestroyInstance();
~CurlDynamicLoader();

bool Initialize();
void Cleanup();
bool IsLoaded() const { return m_curlLibrary != nullptr; }

private:
CurlDynamicLoader() = default;

bool LoadFunction(FARPROC& funcPtr, const char* functionName);

HMODULE m_curlLibrary = nullptr;

// Thread safety
static std::mutex s_initMutex;
static HC_UNIQUE_PTR<CurlDynamicLoader> s_instance;
};

} // httpclient
} // xbox

// GDK macro variants: route through dynamic loader and provide default returns when not loaded
#define CURL_CALL(func_name) ::xbox::httpclient::CurlDynamicLoader::GetInstance().func_name##_fn
#define CURL_INVOKE_OR(defaultRet, func, ...) \
((::xbox::httpclient::CurlDynamicLoader::GetInstance().IsLoaded()) ? \
(::xbox::httpclient::CurlDynamicLoader::GetInstance().func##_fn(__VA_ARGS__)) : \
(defaultRet))
// Convenience when defaultRet == 0 (common for void-calls or zero-initialized return types)
#define CURL_INVOKE(func, ...) CURL_INVOKE_OR(0, func, __VA_ARGS__)

#else // non-GDK

// Non-GDK macro variants: call directly
#define CURL_CALL(func_name) func_name
#define CURL_INVOKE_OR(defaultRet, func, ...) func(__VA_ARGS__)
// Convenience when defaultRet == 0
#define CURL_INVOKE(func, ...) func(__VA_ARGS__)

#endif // HC_PLATFORM == HC_PLATFORM_GDK
24 changes: 16 additions & 8 deletions Source/HTTP/Curl/CurlEasyRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ CurlEasyRequest::CurlEasyRequest(CURL* curlEasyHandle, HCCallHandle hcCall, XAsy

CurlEasyRequest::~CurlEasyRequest()
{
curl_easy_cleanup(m_curlEasyHandle);
curl_slist_free_all(m_headers);
(void)CURL_INVOKE(curl_easy_cleanup, m_curlEasyHandle);
(void)CURL_INVOKE(curl_slist_free_all, m_headers);
}

Result<HC_UNIQUE_PTR<CurlEasyRequest>> CurlEasyRequest::Initialize(HCCallHandle hcCall, XAsyncBlock* async)
{
CURL* curlEasyHandle{ curl_easy_init() };
#if HC_PLATFORM == HC_PLATFORM_GDK
// Ensure curl is loaded
if (!CurlDynamicLoader::GetInstance().IsLoaded())
{
HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize: XCurl.dll not available");
return E_HC_XCURL_REQUIRED;
}
#endif
CURL* curlEasyHandle{ CURL_CALL(curl_easy_init)() };
if (!curlEasyHandle)
{
HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize:: curl_easy_init failed");
Expand Down Expand Up @@ -175,7 +183,7 @@ void CurlEasyRequest::Complete(CURLcode result)
HC_TRACE_INFORMATION(HTTPCLIENT, "CurlEasyRequest::m_errorBuffer='%s'", m_errorBuffer);

long platformError = 0;
auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError);
auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError);
if (curle != CURLE_OK)
{
return Fail(HrFromCurle(curle));
Expand All @@ -184,13 +192,13 @@ void CurlEasyRequest::Complete(CURLcode result)
HRESULT hr = HCHttpCallResponseSetNetworkErrorCode(m_hcCallHandle, E_FAIL, static_cast<uint32_t>(platformError));
assert(SUCCEEDED(hr));

hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, curl_easy_strerror(result));
hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, CURL_CALL(curl_easy_strerror)(result));
assert(SUCCEEDED(hr));
}
else
{
long httpStatus = 0;
auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus);
auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus);
if (curle != CURLE_OK)
{
return Fail(HrFromCurle(curle));
Expand Down Expand Up @@ -224,7 +232,7 @@ HRESULT CurlEasyRequest::AddHeader(char const* name, char const* value) noexcept
assert(written == required);
(void)written;

m_headers = curl_slist_append(m_headers, header.c_str());
m_headers = CURL_CALL(curl_slist_append)(m_headers, header.c_str());

return S_OK;
}
Expand Down Expand Up @@ -368,7 +376,7 @@ size_t CurlEasyRequest::WriteHeaderCallback(char* buffer, size_t size, size_t ni
size_t CurlEasyRequest::GetResponseContentLength(CURL* curlHandle)
{
curl_off_t contentLength = 0;
curl_easy_getinfo(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength);
CURL_CALL(curl_easy_getinfo)(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength);
return contentLength;
}

Expand Down
13 changes: 5 additions & 8 deletions Source/HTTP/Curl/CurlEasyRequest.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#pragma once

#if HC_PLATFORM == HC_PLATFORM_GDK
// When developing titles for Xbox consoles, you must use WinHTTP or xCurl.
// See https://docs.microsoft.com/en-us/gaming/gdk/_content/gc/networking/overviews/web-requests/http-networking for detail
#include <XCurl.h>
#else
// This path is untested, but this http provider should work with other curl implementations as well.
// Always include CurlDynamicLoader.h for macros and (on GDK) loader type
#include "CurlDynamicLoader.h"
// Http provider should work with other curl implementations as well.
// The logic in CurlMulti::Perform is optimized for XCurl, but should work on any curl implementation.
#if HC_PLATFORM != HC_PLATFORM_GDK
#include <curl/curl.h>
#endif
#include "Result.h"
Expand Down Expand Up @@ -79,7 +76,7 @@ class CurlEasyRequest
template<typename T>
HRESULT CurlEasyRequest::SetOpt(CURLoption option, typename OptType<T>::type v) noexcept
{
CURLcode result = curl_easy_setopt(m_curlEasyHandle, option, v);
CURLcode result = CURL_CALL(curl_easy_setopt)(m_curlEasyHandle, option, v);
if (result != CURLE_OK)
{
HC_TRACE_ERROR(HTTPCLIENT, "curl_easy_setopt(request, %d, value) failed with %d", option, result);
Expand Down
Loading