> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 调试自定义 HTTP 堆栈

> 调试自定义 HTTP 堆栈

当你的游戏直接使用自定义 HTTP 堆栈而非 xCurl，并需要通过 Fiddler 等代理调试流量时，请参阅本文。

## 何时适用本指南

如果满足以下一项或多项条件，此指南尤为有用：

* 你的游戏需要直接检查或应用代理设置。
* 你的调试流程依赖 Fiddler 或其他开发代理。
* 你的 HTTP 堆栈未使用 Schannel 作为安全提供程序，必须显式加载代理证书。

有关一般安全指导，请参阅 [面向 Microsoft 游戏开发工具包游戏的安全 Web 请求与 WebSocket 最佳实践](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/game-principles/security/communication-security/communication-security-impl/gc-secure-webrequest-impl)。

## XBOX 与 PC 上的代理设置

在 XBOX 上处理代理设置时，如果你的构建和目标环境提供了该路径，优先使用带 `WinHttpProxySettingsTypeXBox` 的 `WinHttpGetProxySettingsEx`。在 XBOX 上，从 April 2026 Microsoft 游戏开发工具包 (GDK) 开始，此 API 是读取代理设置的前瞻性 API。

如果你的游戏面向 XBOX 上更早的 GDK，XBOX 特定的代理设置路径不可用。此时应在 HTTP 堆栈中手动设置代理地址和端口。即便在这些较旧的 GDK 上 XBOX 特定的代理地址 API 不可用，本文后面所述的证书加载路径仍然适用。

PC 游戏也可以通过 WinHTTP 查询代理设置，但不要直接照搬 XBOX 的流程。在 PC 上，`WinHttpGetProxySettingsEx` 是异步的，因此请先等待操作完成，再调用 `WinHttpGetProxySettingsResultEx` 并将返回的代理值复制到你的 HTTP 堆栈中。

以下是一个基于 libcurl 的示例：使用 XBOX 代理设置 API 解析代理地址，然后直接传给堆栈。使用 `WINHTTP_FLAG_ASYNC` 打开会话并注册状态回调，以便代码同时处理同步完成与 `ERROR_IO_PENDING` 返回。以下示例演示了调用流程：

```cpp theme={null}
// WinHttpCreateProxyResolver is not declared in WinHttp.h when using the GDK.
WINHTTPAPI DWORD WINAPI WinHttpCreateProxyResolver(HINTERNET hSession, HINTERNET* phResolver);

std::string ResolveXboxProxy()
{
    HINTERNET session = WinHttpOpen(
        L"CustomHttp/1.0",
        WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        WINHTTP_FLAG_ASYNC);
    if (!session)
        return {};

    struct ProxyContext
    {
        HANDLE event;
        DWORD  error;
    };

    ProxyContext ctx{ CreateEventW(nullptr, TRUE, FALSE, nullptr), ERROR_SUCCESS };

    WinHttpSetStatusCallback(session,
        [](HINTERNET, DWORD_PTR context, DWORD status, LPVOID info, DWORD)
        {
            auto* c = reinterpret_cast<ProxyContext*>(context);
            if (status == WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE)
            {
                c->error = ERROR_SUCCESS;
                SetEvent(c->event);
            }
            else if (status == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR)
            {
                c->error = reinterpret_cast<WINHTTP_ASYNC_RESULT*>(info)->dwError;
                SetEvent(c->event);
            }
        },
        WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS,
        0);

    HINTERNET resolver = nullptr;
    if (WinHttpCreateProxyResolver(session, &resolver) != ERROR_SUCCESS)
    {
        CloseHandle(ctx.event);
        WinHttpCloseHandle(session);
        return {};
    }

    DWORD result = WinHttpGetProxySettingsEx(
        resolver,
        WinHttpProxySettingsTypeXBox,
        nullptr,
        reinterpret_cast<DWORD_PTR>(&ctx));

    if (result == ERROR_IO_PENDING)
    {
        // Wait for the async callback to fire (5-second timeout).
        WaitForSingleObject(ctx.event, 5000);
        result = ctx.error;
    }

    std::string proxyAddress;
    if (result == ERROR_SUCCESS)
    {
        WINHTTP_PROXY_SETTINGS_EX proxySettings{};
        if (WinHttpGetProxySettingsResultEx(resolver, &proxySettings) == ERROR_SUCCESS)
        {
            PCWSTR proxy =
                proxySettings.pcwszSecureProxy != nullptr
                    ? proxySettings.pcwszSecureProxy
                    : proxySettings.pcwszProxy;

            if (proxy != nullptr)
            {
                // Example function for conversion.
                proxyAddress = Utf16ToUtf8(std::wstring(proxy));
            }

            WinHttpFreeProxySettingsEx(WinHttpProxySettingsTypeXBox, &proxySettings);
        }
    }

    CloseHandle(ctx.event);
    WinHttpCloseHandle(resolver);
    WinHttpCloseHandle(session);
    return proxyAddress;
}

std::string proxyAddress = ResolveXboxProxy();
if (!proxyAddress.empty())
{
    curl_easy_setopt(curlHandle, CURLOPT_PROXY, proxyAddress.c_str());
}
```

## TLS 提供程序行为

### 基于 Schannel 的堆栈

如果你的堆栈依赖 Schannel 支持的 TLS 行为，主机会自动应用你配置的代理证书。此行为同样适用于以 Schannel 作为 TLS 提供程序的 libcurl 构建。

### 非 Schannel 堆栈

如果你的堆栈依赖 OpenSSL 或其他非 Schannel TLS 提供程序，游戏需自行负责显式加载当前调试工具所需的证书。将此证书加载路径保留在你的发布代码中。对于主机端，游戏还应在 `RETAIL` 构建中保留相关的代理调试路径可用，以便在必要时仍能对通过代理的流量进行诊断。

有关信任、代理与安全默认值的指导，请参阅 [面向 Microsoft 游戏开发工具包游戏的安全 Web 请求与 WebSocket 最佳实践](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/game-principles/security/communication-security/communication-security-impl/gc-secure-webrequest-impl)。

## 查找代理根证书

对于非 Schannel 堆栈，显式加载与调试工具对应的证书，并将此路径保留在发布代码中。证书位置和使用者取决于工具。例如，[XBOX Multiplayer Analysis Tool (XMAT)](https://aka.ms/XMAT) 在 `CurrentUser\Root` 中使用证书 `Xbox Multiplayer Analysis Tool Root Cert Authority`。以下示例查找该证书并将 DER 编码的证书字节复制到游戏所拥有的缓冲区。

```cpp theme={null}
#include <wincrypt.h>
#include <vector>

HRESULT ExtractXmatRootCertificateDer(_Out_ std::vector<uint8_t>& encodedCertificate)
{
    encodedCertificate.clear();

    constexpr DWORD certificateStoreLocation = CERT_SYSTEM_STORE_CURRENT_USER;
    constexpr wchar_t certificateStoreName[] = L"Root";
    constexpr wchar_t certificateSubjectName[] =
        L"Xbox Multiplayer Analysis Tool Root Cert Authority";

    HCERTSTORE certificateStore = CertOpenStore(
        CERT_STORE_PROV_SYSTEM_W,
        0,
        0,
        certificateStoreLocation |
            CERT_STORE_OPEN_EXISTING_FLAG |
            CERT_STORE_READONLY_FLAG,
        certificateStoreName);
    if (certificateStore == nullptr)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    PCCERT_CONTEXT certificate = CertFindCertificateInStore(
        certificateStore,
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        0,
        CERT_FIND_SUBJECT_STR_W,
        certificateSubjectName,
        nullptr);
    if (certificate == nullptr)
    {
        HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
        CertCloseStore(certificateStore, 0);
        return hr;
    }

    encodedCertificate.assign(
        certificate->pbCertEncoded,
        certificate->pbCertEncoded + certificate->cbCertEncoded);

    CertFreeCertificateContext(certificate);
    CertCloseStore(certificateStore, 0);
    return S_OK;
}
```

此步之后，将 `encodedCertificate` 传给你所用 TLS 提供程序的证书加载路径即可。

## 推荐调试流程

1. 按 [XBOX 开发套件上的 Fiddler](/build/console-features/networking/tools/fiddler-setup-networking) 中所述，在 XBOX Device Portal 中配置主机代理设置。
2. 如果你在 April 2026 GDK 或更高版本上面向 XBOX 且游戏需要直接检查代理设置，请使用带 `WinHttpProxySettingsTypeXBox` 的 `WinHttpGetProxySettingsEx`。
3. 如果 PC 代码路径通过 WinHTTP 查询代理设置，请等待异步结果完成后再调用 `WinHttpGetProxySettingsResultEx` 并应用返回的代理值。
4. 如果你在 XBOX 上面向更早的 GDK，或使用不消费 XBOX 特定代理设置路径的堆栈，请在游戏中手动设置代理地址和端口。
5. 如果你使用 Schannel，可依赖由主机应用的证书。如果你使用 OpenSSL 或其他堆栈，请从证书存储中检索所用调试工具对应的证书并显式加载。

## 相关页面

* [XBOX 开发套件上的 Fiddler](/build/console-features/networking/tools/fiddler-setup-networking)
* [面向 Microsoft 游戏开发工具包游戏的安全 Web 请求与 WebSocket 最佳实践](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/game-principles/security/communication-security/communication-security-impl/gc-secure-webrequest-impl)


## Related topics

- [XBOX 上的 Web 请求与 HTTP 堆栈](/zh-CN/build/console-features/networking/web-requests/index.md)
- [库存堆栈](/zh-CN/services/playfab/economy-monetization/economy-v2/inventory/stacks.md)
- [编写自定义 CloudScript](/zh-CN/services/playfab/live-service-management/service-gateway/automation/cloudscript/writing-custom-cloudscript.md)
- [使用自定义分辨率进行测试](/zh-CN/build/core-features/common/game-streaming/game-streaming-testing-custom-resolution.md)
- [在 VM 创建期间运行自定义脚本(预览)](/zh-CN/services/playfab/multiplayer/servers/vmstartupscript.md)
