> ## 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.

# WinHTTP 概述

> WinHTTP 概述

本文介绍如何在 Microsoft 游戏开发工具包 (GDK) 游戏中使用 [Windows HTTP Services (WinHTTP)](https://learn.microsoft.com/windows/desktop/winhttp/winhttp-start-page) 功能。它是可用于 PC 和 XBOX 主机 Microsoft 游戏开发工具包 (GDK) 游戏的较底层 HTTP 客户端 API。你可以使用它创建常规 HTTP 与 WebSocket 服务端点。

由于它较为底层，实现时需要考虑更多因素，也需要更多步骤才能实现安全且健壮的通信。我们建议你的游戏实现遵循所有 [通信安全最佳实践 (NDA 文章)](/build/game-principles/security/communication-security-overview)。

## WinHTTP 版本差异

总的来说，Microsoft 游戏开发工具包 (GDK) 游戏与 WinHTTP 的交互方式与 Win32 应用程序与 WinHTTP 的交互方式相同。

在为 Microsoft 游戏开发工具包 (GDK) 游戏开发时，只有 WinHTTP 的扁平 C/C++ API 可用。这意味着 HTTP 功能必须基于此 HTTP 客户端 API 构建。

## 将 WinHTTP 添加到 XBOX 主机项目

在主机端，源文件中应 `#include <winhttp.h>`。必须链接 `XGamePlatform.lib`，而不是直接链接 `Winhttp.lib`。在 Microsoft 游戏开发工具包 (GDK) 游戏中，只有 `WINAPI_PARTITION_GAMES` API 系列下的 API 可用。在 Windows PC 上继续链接 `Winhttp.lib`。

有关如何将 WinHTTP 集成到 Microsoft 游戏开发工具包 (GDK) 游戏的示例，请参阅 [SimpleWinHttp 示例](https://aka.ms/gdkdl)。它为你自己的 WinHTTP 实现提供了一个良好的起点，并包含 `WinHttpManager` 类，暴露了简单的异步 API 面。

## 网络初始化与 WinHTTP

在你的游戏首次调用 [WinHttpOpen](https://learn.microsoft.com/windows/desktop/api/winhttp/nf-winhttp-winhttpopen) 之前，Microsoft 游戏开发工具包 (GDK) 游戏必须确保网络堆栈已初始化。如果在游戏启动过程中过早调用 `WinHttpOpen`，那么 `WinHttpOpen` 或后续 WinHTTP 调用可能以不确定的方式失败或崩溃。请求可能表面上成功但实际上失败，反之亦然，直到网络被声明为已初始化为止。有关如何判断网络堆栈已初始化的详细信息，请参阅 [网络初始化](/build/console-features/networking/initialization-connectivity-networking#network-initialization)。

## 游戏挂起/恢复与 WinHTTP

收到游戏挂起通知时，游戏应启动关闭所有 WinHTTP 句柄的过程。WinHTTP 句柄清理是异步的。因此，应按以下顺序关闭句柄：先关闭所有请求句柄，然后关闭所有连接句柄，最后关闭所有会话句柄。WinHTTP 句柄清理之所以是异步的，是为了确保通知线程的安全。虽然是异步的，但 WinHTTP 句柄清理不会延迟任何时间，可以轻松放入一秒的挂起延迟超时内。

在恢复时，游戏应按前述“网络初始化与 WinHTTP”节所述的步骤操作，等待网络回到就绪状态后再继续使用 WinHTTP。挂起与恢复事件之间可能间隔很长时间，网络需要重新稳定后 WinHTTP API 才能再次表现出确定性。

## 内存与并发注意事项

并发 WinHTTP 请求的数量应始终保持在 8 个以下，以确保 WinHTTP 内部的异步状态能正常工作并保持在其内存预算之内。此限制适用于游戏运行时中所有并发操作，包括来自 XBOX 服务 API 和 `XCurl` 的调用。

作为 [WinSock 内存注意事项](/build/console-features/networking/game-mesh/winsock-intro-networking#SocketMemory) 的补充，接收数据时应始终保证有一个通过 `WinHttpReadData` 挂起的缓冲区（或正在等待 `WinHttpQueryDataAvailable` 调用的回调），以尽快将数据从内核态内存池转移到你的用户态进程中，最大限度地减少 HTTP 操作所消耗的内核内存。

`WinHttpQueryHeaders` 的 getter 函数需要临时的内存分配。它会在内部分配大小等于 `lpdwBufferLength` 参数的临时缓冲区（并在函数返回前释放）。因此，你应使用 `WINHTTP_NO_OUTPUT_BUFFER` 双次调用模式来尽量减小临时缓冲区大小，并限制同时进行的 `WinHttpQueryHeaders` 调用数量，以避免占用过多系统内存导致系统不稳定。头部默认最大尺寸为 64 KB，由 `WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE` WinHTTP 选项指定。

## WinHttpOpen 注意事项

### 标志

必须为 [WinHttpOpen](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpopen) 传入下表中的标志。

| 参数                | 值                                     |
| ----------------- | ------------------------------------- |
| `dwAccessType`    | `WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY` |
| `pszProxyW`       | `WINHTTP_NO_PROXY_NAME`               |
| `pszProxyBypassW` | `WINHTTP_NO_PROXY_BYPASS`             |
| `dwFlags`         | `WINHTTP_FLAG_SECURE_DEFAULTS`        |

`WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY`、`WINHTTP_NO_PROXY_NAME` 与 `WINHTTP_NO_PROXY_BYPASS` 的组合允许 Microsoft 游戏开发工具包 (GDK) 平台自动处理诸如 [Fiddler](/build/console-features/networking/tools/fiddler-setup-networking) 以及其他边缘网络环境下的代理。

`WINHTTP_FLAG_SECURE_DEFAULTS` 标志是一个新标志，旨在通过设置推荐的安全连接行为来帮助 Microsoft 游戏开发工具包 (GDK) 游戏遵循安全最佳实践。它在 XBOX One 主机上可用，未来 Windows OS 更新中将可用于 Windows PC。在不支持该标志的现有 Windows OS 版本上传入 `WINHTTP_FLAG_SECURE_DEFAULTS` 会导致无效参数失败。此标志有一个显著副作用——它会强制 WinHTTP 进入异步模式，因为该标志隐式包含了 `WINHTTP_FLAG_ASYNC` 标志。在不支持此标志的 Windows PC OS 版本上，应改为传入 `WINHTTP_FLAG_ASYNC`，以尽量减小其余 WinHTTP 实现上的差异。

<Note>`WINHTTP_FLAG_SECURE_DEFAULTS` 标志要求在 `WinHttpOpenRequest` 中传入匹配的 `WINHTTP_FLAG_SECURE` 标志，并会阻止未加密的 HTTP 请求。在开发套件上进行内部调试与测试时，可以创建一个 WinHTTP 会话句柄并向 `WinHttpOpen` 指定 `WINHTTP_FLAG_ASYNC` 标志。此标志允许你在开发期间发起未加密的 HTTP 请求，只需在 `WinHttpOpenRequest` 中不指定 `WINHTTP_FLAG_SECURE` 标志即可。对于非调试流量，仍应使用以 `WINHTTP_FLAG_SECURE_DEFAULTS` 打开的会话句柄，以匹配游戏在 RETAIL 中看到的请求行为。</Note>

### WINHTTP\_OPTION\_SECURE\_PROTOCOLS

在使用 `WinHttpOpen` 创建新的会话句柄之后，必须调用 [WinHttpSetOption](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption)，选项为 `WINHTTP_OPTION_SECURE_PROTOCOLS`，并传入与将要在此会话句柄上使用的匹配 URL 相对应、通过调用 [XNetworkingQuerySecurityInformationForUrlUtf16Async](/reference/networking/xnetworking/functions/xnetworkingquerysecurityinformationforurlutf16async) 获取的 `XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags`。你还应将 [XNetworkingSecurityInformation](/reference/networking/xnetworking/structs/xnetworkingsecurityinformation) 结构存储在你的上下文对象中，以便稍后在验证 TLS/SSL 握手时使用。

### 缓存会话句柄

通过 `WinHttpOpen` 创建的 HTTP 会话句柄从内存角度看代价较高，并且会带来较大的启动开销，从而延迟第一个 HTTP 请求。我们建议你在游戏中尽可能地缓存 HTTP 会话句柄以避免这些开销。

但是，无法在已有会话句柄上更改 `WINHTTP_OPTION_SECURE_PROTOCOLS` 选项。你应保留一个 `XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags` 值到 WinHTTP 会话句柄的缓存映射，以确保每种不同的安全协议标志都对应不同的会话句柄。

游戏维护的缓存必须在收到挂起通知时清除，并应在恢复时（等待网络初始化完成后）从头重建。

## WinHttpConnect 注意事项

与会话句柄不同，通过 [WinHttpConnect](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpconnect) 创建的连接句柄绝不应缓存。每个新请求和/或重试尝试都应创建新的句柄。WinHTTP 连接句柄尽管名为连接，却与底层服务器的 TCP (传输控制协议) 连接没有关系。WinHTTP 通过会话句柄管理底层服务器连接的生命周期，并会尽可能自动地将已打开的服务器连接复用给新的连接句柄。

### URL 规范化

WinHTTP 要求所有 URL 都规范化为 a-z、A-Z 和 0-9 的 US-ASCII 字符。有关规范化的更多信息，请参阅 [WinHTTP 中的 URL](https://learn.microsoft.com/windows/win32/winhttp/uniform-resource-locators--urls--in-winhttp)。可能的情况下，我们建议将游戏使用的 URL 以规范化形式硬编码。这种形式可避免使用 [WinHttpCrackUrl](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpcrackurl) 和 [WinHttpCreateUrl](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpcreateurl) 动态规范化 URL 所带来的内存分配和性能问题。

### URL 拆分

WinHTTP 要求向 `WinHttpConnect` 传入以 null 结尾的主机名字符串，而路径和对象则传给 `WinHttpOpenRequest`。你的游戏在某些地方必须传入拼接后的主机名与路径的完整 URL，而在另一些地方只需传主机名或路径。我们建议你在游戏中硬编码这两者，避免使用 [WinHttpCrackUrl](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpcrackurl) 和 [WinHttpCreateUrl](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpcreateurl) 动态拼接或拆分 URL。

## WinHttpOpenRequest 注意事项

与 WinHTTP 连接句柄类似，通过 [WinHttpOpenRequest](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpopenrequest) 函数创建的 WinHTTP 请求句柄绝不应缓存。每个新请求和/或重试尝试都应创建新的句柄。

作为安全最佳实践，在调用 `WinHttpOpenRequest` 函数时游戏应始终为 `dwFlags` 参数传入 `WINHTTP_FLAG_SECURE` 标志。

## 检索并应用 XBOX 服务令牌

Microsoft 游戏开发工具包 (GDK) 游戏不会自动插入令牌。相反，游戏应使用 Microsoft 游戏开发工具包 (GDK) 的 [XUser](/reference/system/xuser/xuser_members) API 检索 XBOX 服务身份验证令牌与签名。当游戏拥有用户后，应对每个请求分别调用 [XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async) 来获取令牌与签名字符串。然后应将这两个字符串作为标头传给对 `WinHttpAddRequestHeadersEx`、[WinHttpSendRequest](https://learn.microsoft.com/windows/desktop/api/winhttp/nf-winhttp-winhttpsendrequest) 或 [WinHttpAddRequestHeaders](https://learn.microsoft.com/windows/desktop/api/winhttp/nf-winhttp-winhttpaddrequestheaders) 的调用。

为生成正确的签名，[XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async) 需要游戏传入所有标头以及整个正文。对于带有大正文的 `POST` 或 `PUT`，游戏可传入在 Partner Center 中配置的正文子集。有关更多信息，请参阅 [Web 服务 (NDA 主题)](/services/xbox-services/fundamentals/s2s-auth-calls/custom-service-config/web-services/live-web-services-nav)。目前 XBOX 网络未提供检索此配置的机制，客户端应硬编码这些值或通过自定义的游戏特定端点获取它们。

[XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async) 在内部执行所有必要的缓存，对每次 HTTP 尝试（包括重试）都应调用一次。如果游戏在任何 HTTP 请求上收到 401 Unauthorized HTTP 响应状态码，游戏应重试请求并强制刷新 XBOX 服务身份验证令牌。方法是通过 [XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async) 获取新令牌，并传入 [XUserGetTokenAndSignatureOptions::ForceRefresh](/reference/system/xuser/enums/xusergettokenandsignatureoptions) 枚举值。

当游戏拿到通过 [XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async) 获取的 [XUserGetTokenAndSignatureUtf16Data](/reference/system/xuser/structs/xusergettokenandsignatureutf16data) 后，游戏必须将 `XUserGetTokenAndSignatureUtf16Data::Token` 和 `XUserGetTokenAndSignatureUtf16Data::Signature` 转换为 HTTP 标头传给 WinHTTP。为降低 Microsoft 游戏开发工具包 (GDK) 游戏的复杂度，专门新增了一个 WinHTTP API `WinHttpAddRequestHeadersEx`。下面展示了如何使用此新 API 的示例。该新 API 在 XBOX One 主机上可用，未来 Windows OS 更新中将可用于 Windows PC。在主机上，我们建议使用 `WinHttpAddRequestHeadersEx` 以避免额外的分配和字符串格式变更。

```cpp theme={null}
HRESULT
AddTokenAndSignatureDataToHttpRequest(
    XUserGetTokenAndSignatureUtf16Data* userTokenAndSignatureData,
    HINTERNET requestHandle
    )
{
    WINHTTP_EXTENDED_HEADER winhttpHeader[2];
    winhttpHeader[0].pwszName = L"Authorization";
    winhttpHeader[0].pwszValue = userTokenAndSignatureData->token;
    winhttpHeader[1].pwszName = L"Signature";
    winhttpHeader[1].pwszValue = userTokenAndSignatureData->signature;
    return HRESULT_FROM_WIN32(WinHttpAddRequestHeadersEx(
        m_handshakeRequest,
        WINHTTP_ADDREQ_FLAG_ADD,
        WINHTTP_EXTENDED_HEADER_FLAG_UNICODE,
        0,
        tokenAndSignature->signatureCount ? 2 : 1,
        winhttpHeader));
}

```

<Note>设备或已登录账户需要有权访问其所设定的沙盒。否则，`XUserGetTokenAndSignatureUtf16Data` 会失败。</Note>

### 使用 XBOX Network Security Authorization List (NSAL)

XBOX 网络使用 NSAL 来确保客户端与你的 Web 服务之间建立安全且经过身份认证的连接。游戏在 Partner Center 中作为其配置的一部分管理 NSAL 的内容。有关更多信息，请参阅 [在 Partner Center 中设置 Web 服务 (NDA 主题)](/services/xbox-services/fundamentals/s2s-auth-calls/custom-service-config/web-services/live-web-services)。随后每个游戏都会自动下载 NSAL 配置。它既用于生成合适的 XBOX 服务令牌，也用于为游戏的特定端点执行证书固定 (certificate pinning)。}

## WinHTTP 异步状态机注意事项

WinHTTP 异步状态机在主机与 Windows PC 上相同。要为一个或多个通知注册回调函数，请使用 [WinHttpSetStatusCallback](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpsetstatuscallback) 函数。出于调试目的，建议为 `dwNotificationFlags` 参数使用 `WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS` 标志，因为 WinHTTP 对其运行情况相对详细透明。虽然大多数通知不需要你做任何操作，但记录数据有助于发现问题的根本原因。

WinHTTP 使用单一线程发送通知。游戏应尽可能避免阻塞任何通知函数，否则会导致进程中所有 HTTP 请求都无法推进。未处理的通知也会增加内核态内存，可能导致崩溃。

WinHTTP 不会复制你的发送或接收缓冲区，要求你在对应的完成回调到达之前保持这些缓冲区处于分配状态。请务必从调用 [WinHttpSendRequest](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest) 起，直到收到相应的 `WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE` 通知之前，保持发送缓冲区处于分配且有效的状态。同样地，每次调用 [WinHttpReadData](https://learn.microsoft.com/windows/win32/api/winhttp/nf-winhttp-winhttpreaddata) 后，都要保证接收缓冲区在收到对应的 `WINHTTP_CALLBACK_STATUS_READ_COMPLETE` 通知之前保持分配且有效。我们还建议接收缓冲区大小至少为 8 KB，以避免可能导致栈耗尽的递归问题。

游戏还应确保 WinHTTP 缓冲区能正确清空，方法是持续进行异步的 `WinHttpQueryDataAvailable`/`WinHttpReadData` 循环，并且不长时间阻塞 WinHTTP 回调。

### 验证 TLS (Transport Layer Security)/SSL (Secure Sockets Layer) 握手

作为安全最佳实践，游戏应对 TLS/SSL 握手进行验证，并只使用 TLS 1.2。

额外的验证在 `WINHTTP_CALLBACK_STATUS_SENDING_REQUEST` 通知中进行。在此通知中，必须调用 [XNetworkingVerifyServerCertificate](/reference/networking/xnetworking/functions/xnetworkingverifyservercertificate) 函数并传入先前对 [XNetworkingQuerySecurityInformationForUrlUtf16Async](/reference/networking/xnetworking/functions/xnetworkingquerysecurityinformationforurlutf16async) 相应调用中获取的 [XNetworkingSecurityInformation](/reference/networking/xnetworking/structs/xnetworkingsecurityinformation) 结构。如果证书链无效，此函数会失败。你应在回调完成之前 *立即* 关闭 WinHTTP 句柄，确保没有数据被传输到或来自已被入侵的服务器。

除验证证书链外，[XNetworkingVerifyServerCertificate](/reference/networking/xnetworking/functions/xnetworkingverifyservercertificate) 函数也是主机端 Fiddler 功能所必需的。

## 调试 WinHTTP

[Fiddler](/build/console-features/networking/tools/fiddler-setup-networking) 是查看和调试 WinHTTP 流量的实用工具。为使 Fiddler 能捕获你的游戏流量，你必须为 `WinHttpOpen` 传入 `WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY`、`WINHTTP_NO_PROXY_NAME` 和 `WINHTTP_NO_PROXY_BYPASS` 标志。还必须在 `WINHTTP_CALLBACK_STATUS_SENDING_REQUEST` 通知回调中调用 [XNetworkingVerifyServerCertificate](/reference/networking/xnetworking/functions/xnetworkingverifyservercertificate)。

<Note>HTTP Monitor 不能用于 Microsoft 游戏开发工具包 (GDK) 游戏。</Note>

## 参考 API 文档

* [Xuser (API 内容)](/reference/system/xuser/xuser_members)
  * 函数
    * [XUserGetTokenAndSignatureUtf16Async](/reference/system/xuser/functions/xusergettokenandsignatureutf16async)
  * 结构
    * [XUserGetTokenAndSignatureUtf16Data](/reference/system/xuser/structs/xusergettokenandsignatureutf16data)
* [xnetworking (API 内容)](/reference/networking/xnetworking/xnetworking_members)
  * 函数
    * [XNetworkingQuerySecurityInformationForUrlUtf16Async](/reference/networking/xnetworking/functions/xnetworkingquerysecurityinformationforurlutf16async)
    * [XNetworkingVerifyServerCertificate](/reference/networking/xnetworking/functions/xnetworkingverifyservercertificate)
  * 结构
    * [XNetworkingSecurityInformation](/reference/networking/xnetworking/structs/xnetworkingsecurityinformation)

## 另请参阅

[Windows HTTP Services (WinHTTP)](https://learn.microsoft.com/windows/desktop/winhttp/winhttp-start-page)

[XSAPI C 概述 (安全链接)](https://developer.microsoft.com/games/xbox/docs/gdk/atoc-xsapi-c)

[XUser](/reference/system/xuser/xuser_members)

[在 Partner Center 中设置 Web 服务 (NDA 文章)](/services/xbox-services/fundamentals/s2s-auth-calls/custom-service-config/web-services/live-web-services)

[XBOX One 主机上的 Fiddler](/build/console-features/networking/tools/fiddler-setup-networking)

[通信安全最佳实践概述 (NDA 文章)](/build/game-principles/security/communication-security-overview)


## Related topics

- [Web 请求](/zh-CN/build/console-features/networking/web-requests/web-requests-toc.md)
- [XNetworkingSecurityInformation](/zh-CN/reference/networking/xnetworking/structs/xnetworkingsecurityinformation.md)
- [XNetworkingQuerySecurityInformationForUrlUtf16Async](/zh-CN/reference/networking/xnetworking/functions/xnetworkingquerysecurityinformationforurlutf16async.md)
- [XNetworkingVerifyServerCertificate](/zh-CN/reference/networking/xnetworking/functions/xnetworkingverifyservercertificate.md)
- [XUserGetTokenAndSignatureUtf16Async](/zh-CN/reference/system/xuser/functions/xusergettokenandsignatureutf16async.md)
