WinHTTP 概览

本主题说明如何将 Windows HTTP 服务 (WinHTTP) 功能用于 Microsoft 游戏开发工具 (GDK) 游戏。 它是一个较低级别的 HTTP 客户端 API,可用于电脑和 Xbox 主机的 Microsoft 游戏开发工具包(GDK)。 可以使用它来创建常规 HTTP 和 WebSocket 服务终结点。

由于它是较低级别的,因此需要实施其他注意事项和步骤,以便可以为游戏实现安全和可靠的通信。 我们建议游戏实现遵循所有 通信安全最佳做法(NDA 主题)要求授权

WinHTTP 版本差异

通常,Microsoft 游戏开发工具包 (GDK) 游戏与 WinHTTP 交互的方式与它们在 Win32 应用程序中与 WinHTTP 交互的方式相同。

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

将 WinHTTP 添加到 Xbox 主机项目

在主机上,你应该在源文件中 #include <winhttp.h>。 必须针对 XGamePlatform.lib 链接,而不是针对 Winhttp.lib 直接链接。 只有 WINAPI_PARTITION_GAMES API 系列下的 API 才能用于 Microsoft 游戏开发工具包 (GDK) 游戏。 在 Windows 电脑上,你应该继续链接Winhttp.lib

有关如何将 WinHTTP 集成到你的 Microsoft 游戏开发工具包 (GDK) 游戏的示例,请参阅 SimpleWinHttp 示例。 该实例为您实现自己的 WinHTTP 提供了一个完全兼容的有力起点,其中包含的 WinHttpManager 类。 它公开了一个简单的异步 API 图面。

网络初始化和 WinHTTP

在第一次调用 WinHttpOpen 之前,Microsoft 游戏开发工具包 (GDK) 游戏必须确保网络堆栈已初始化。 如果在游戏启动过程中过早调用 WinHttpOpen,则 WinHttpOpen 或后续 WinHTTP 调用可能失败或以不确定的方式崩溃。 请求可能显示为成功,但实际上在网络声明为已初始化之前失败,反之亦然。 有关如何确定网络堆栈何时初始化完毕的详细信息,请参阅网络初始化连接

游戏暂停/继续和 WinHTTP

在收到某一游戏挂起通知时,游戏应该开始该进程以便关闭所有 WinHTTP 句柄。 WinHTTP 句柄清理是异步操作。 如此一来,应按以下顺序关闭句柄:所有请求句柄、然后是所有连接句柄、然后是所有会话句柄。 WinHTTP 句柄清理的异步特性可确保通知线程安全。 即使 WinHTTP 句柄清理是异步的,它任何时候都不会延迟。 所以它很适合一秒、挂起延迟的超时。

在恢复后,游戏应该按照网络初始化和 WinHTTP 中所述相同的步骤执行,并且等待网络返回到就绪状态,再继续 WinHTTP 的使用。 在 WinHTTP API 再次变得确定性之前,在挂起和恢复事件之间可能有一段较长的时间要求网络再次稳定。

内存和并发注意事项

并发 WinHTTP 请求数应始终保留在8以下,以确保 WinHTTP 中的 asyncronous 状态正确操作,并在内存预算内。 此限制适用于标题运行库中的所有并发操作,包括从 Xbox Service Api 和 XCurl 中的调用。

作为 WinSock 内存注意事项扩展,应确保在接收数据时,始终使用 WinHttpReadData 挂起缓冲区(或等待 WinHttpQueryDataAvailable 调用的回调),以尽快将数据从内核模式内存池传输到用户模式进程中,并最大程度地减少 HTTP 操作消耗的内核内存量。

WinHttpQueryHeaders getter 函数需要瞬时内存分配。 它为内部使用分配了一个大小等于 lpdwBufferLength 参数的暂存缓冲区(并在函数返回之前将其释放)。 因此,应使用 WINHTTP_NO_OUTPUT_BUFFER 双重调用模式来最大程度地减少暂存缓冲区的大小,并限制一次对 WinHttpQueryHeaders 进行的并发调用次数,避免可能导致系统不稳定的过多系统内存使用情况。 请注意,由 WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE WinHTTP 选项指定,标头的默认最大大小为 64KB。

WinHttpOpen 注意事项

标记

您必须将以下表中的标记传递给 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_PROXYWINHTTP_NO_PROXY_NAMEWINHTTP_NO_PROXY_BYPASS 可允许 Microsoft 游戏开发工具包 (GDK) 平台自动处理代理(例如,Fiddler)和其他边界网络环境。

WINHTTP_FLAG_SECURE_DEFAULTS 标志是一个新标志,专门用于通过设置建议的安全连接行为来帮助 Microsoft 游戏开发工具包 (GDK) 游戏遵守安全最佳做法。 Xbox One 主机上提供了此标志,并且在未来的 Windows 操作系统更新中,将在 Windows 电脑上提供此标志。 尝试在现有 Windows 操作系统版本上传递 WINHTTP_FLAG_SECURE_DEFAULTS 会导致无效参数故障。 此标记的一个较大的副作用是,由于它隐式包含 WINHTTP_FLAG_ASYNC 标记,因此,会强制 WinHTTP 进入异步模式。 因此,在不支持此标志的 Windows 电脑操作系统版本中,您应传递 WINHTTP_FLAG_ASYNC,以最大程度地减少其余 WinHTTP 实现中的差异。

注意

WINHTTP_FLAG_SECURE_DEFAULTS标志需要传递给WinHttpOpenRequest的匹配WINHTTP_FLAG_SECURE标志,并阻止未加密的 HTTP 请求。 在用于内部调试和测试的开发套件上,可以创建一个额外的 WinHTTP 会话句柄,并将 WINHTTP_FLAG_ASYNC 标记指定为 WinHttpOpen。 这样,就可以通过在开发过程中通过不将 WINHTTP_FLAG_SECURE 标志指定为 WinHttpOpenRequest 来发出未加密的 HTTP 请求。 对于非调试流量,仍可使用 WINHTTP_FLAG_SECURE_DEFAULTS 打开的会话句柄,以匹配 RETAIL 游戏中所示的请求行为。

WINHTTP_OPTION_SECURE_PROTOCOLS

WinHttpOpen 创建新的会话句柄后,您必须使用选项 WINHTTP_OPTION_SECURE_PROTOCOLS 调用 WinHttpSetOption ,并传递通过为将使用此会话句柄的匹配 URL 调用 XNetworkingQuerySecurityInformationForUrlUtf16Async 而检索到的相应 XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags。 你还应将 XNetworkingSecurityInformation 结构存储到上下文对象中,以便在验证 TLS/SSL 握手步骤中使用。

缓存会话句柄

从内存角度来看,通过 WinHttpOpen 创建的 HTTP 会话句柄开销较大,会导致较大的启动成本,从而延迟第一个 HTTP 请求。 建议您尽量在游戏中缓存 HTTP 会话句柄,以避免此类成本。

但是,无法更改会话句柄上的 WINHTTP_OPTION_SECURE_PROTOCOLS 选项。 您应保留映射到 WinHTTP 会话句柄的 XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags 值的缓存,以确保对于每个不同的安全协议标记,您有不同的会话句柄。

您的游戏保留的缓存必须在收到挂起通知时清除,并且应在恢复时重新生成(等到网络初始化完成后)。

WinHttpConnect 注意事项

与会话句柄不同,永远不要缓存通过 WinHttpConnect 创建的连接句柄。 应为每个新请求和/或重试尝试创建新句柄。 无论 WinHTTP 连接句柄的名称如何,它们都与基础服务器传输控制协议 (TCP) 连接没有关系。 WinHTTP 通过会话句柄管理基础服务器连接的生存期,并且会在可能的情况下为新连接句柄自动重用打开的服务器连接。

URL 标准化

WinHTTP 要求所有 URL 都标准化为 a-z、A-Z 和 0-9 US-ASCII 字符。 有关标准化的更多信息,请参阅 WinHTTP 中的统一资源定位器 (URL)。 可能的话,推荐硬编码游戏以标准化格式使用的 URL。 这会避免在使用 WinHttpCrackUrlWinHttpCreateUrl 函数动态标准化 URL 时导致的内存分配和性能问题。

URL 拆分

WinHTTP 要求在将路径和对象传递到 WinHttpOpenRequest 时,向 WinHttpConnect 传递一个以 null 结尾的主机名字符串。 这意味着您的游戏必须在某些地方传递包含已标准化的主机名和路径的完整 URL,并且要在其他地方只传递主机名或路径。 我们建议您在游戏中对两者都进行硬编码,以避免需要使用 WinHttpCrackUrlWinHttpCreateUrl 自动标准化或拆分 URL。

WinHttpOpenRequest 注意事项

与 WinHTTP 连接句柄相似,千万不要缓存通过 WinHttpOpenRequest 函数创建的 WinHTTP 请求句柄。 应为每个新请求和/或重试尝试创建新句柄。

作为安全最佳做法,在调用 WinHttpOpenRequest 函数时,游戏应始终传递 dwFlags 参数的 WINHTTP_FLAG_SECURE 标志。

检索和应用 Xbox 服务令牌

Microsoft 游戏开发工具包 (GDK) 游戏中不会自动插入令牌。 相反,该游戏应使用 Microsoft 游戏开发工具包 (GDK) XUser API 检索 Xbox 服务身份验证令牌和签名。 在游戏具有某一用户后,该游戏应调用 XUserGetTokenAndSignatureUtf16Async 以便为每个单独的请求检索令牌和签名字符串。 然后,这两个字符串应该以头文件的形式传递到对 WinHttpAddRequestHeadersExWinHttpSendRequestWinHttpAddRequestHeaders 的调用中。

若要生成正确的签名,XUserGetTokenAndSignatureUtf16Async 希望游戏传递所有标头和整个正文。 对于具有较大正文的 POSTPUT,游戏可传递在合作伙伴中心中配置的正文的子集。 有关详细信息,请参阅 Web 服务(NDA 主题)要求授权。 目前,Xbox 网络未提供用于检索此配置的机制。 客户端或者应对值执行硬编码,或通过自定义的、游戏特定的终结点检索它们。

XUserGetTokenAndSignatureUtf16Async 在内部执行所有必要的缓存,应在每次 HTTP 尝试(包括重试)时调用它。 如果游戏收到针对任何 HTTP 请求的 401 未授权的 HTTP 响应状态代码,则游戏应该重新尝试请求并且强制刷新 Xbox 服务身份验证令牌。 这可通过使用 XUserGetTokenAndSignatureUtf16Async 检索新令牌并传递 XUserGetTokenAndSignatureOptions::ForceRefresh 枚举值来实现。

在通过调用 XUserGetTokenAndSignatureUtf16Async 检索到 XUserGetTokenAndSignatureUtf16Data 之后,游戏必须将 XUserGetTokenAndSignatureUtf16Data::TokenXUserGetTokenAndSignatureUtf16Data::Signature 转换为要传递到 WinHTTP 的 HTTP 头。 专门为降低 Microsoft 游戏开发工具包 (GDK) 游戏的复杂性而添加了一个新 WinHTTP API WinHttpAddRequestHeadersEx。 有关如何使用此新 API 的示例如下所示。 Xbox One 主机上提供了此新 API,并且在未来的 Windows 操作系统更新中,将在 Windows 电脑上提供此新 API。 在主机上建议使用 WinHttpAddRequestHeadersEx,以避免额外分配和字符串格式更改。

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));
}

注意

设备或登录帐户需要有权访问其设置为的沙盒。 否则,XUserGetTokenAndSignatureUtf16Data 会失败。

使用 Xbox 网络安全授权列表 (NSAL)

Xbox 网络使用 NSAL 来确保客户与 Web 服务建立安全且经过验证的连接。 游戏管理 NSAL 的内容,作为其在合作伙伴中心中的配置的一部分。 有关详细信息,请参阅在合作伙伴中心设置 Web 服务(NDA 主题)要求授权。 然后为每个游戏自动下载该 NSAL 配置,并且用于生成相应 Xbox 服务令牌以及为您的游戏的特定终结点执行证书固定。

WinHTTP 异步状态机注意事项

主机上的 WinHTTP 异步状态机与 Windows 电脑上的相同。 要注册会出现一个或多个通知的回调函数,请使用 WinHttpSetStatusCallback 函数。 为了方便调试,我们建议对 dwNotificationFlags 参数使用 WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 标记,因为 WinHTTP 对于自己所进行的操作相对来说解释比较详细和透明。 尽管大多数通知不需要您执行任何操作,但是记录相关数据有助于发现问题的根本原因。

WinHTTP 使用一个线程来处理通知。 因此,应尽可能避免阻止任何通知函数,否则,会阻止处理您进程中的所有 HTTP 请求。 Unserviced 通知可能还会增加内核模式内存,这可能会导致崩溃。

WinHTTP 不会复制您的发送或接收缓冲区,并且在相应的完成回调之前,也不会要求您保留这些分配的缓冲区。 确保从您调用 WinHttpSendRequest,直到收到相应 WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 通知,保留所分配的发送缓冲区,并且确保这些缓冲区有效。 同样,您每次调用 WinHttpReadData 时,都要确保在收到相应 WINHTTP_CALLBACK_STATUS_READ_COMPLETE 通知之前,保留所分配的接收缓冲区,并保证这些缓冲区有效。 我们还建议使用至少 8KB 大小的接收缓冲区,以避免可能导致堆栈耗尽的递归问题。

标题还应确保通过持续异步 WinHttpQueryDataAvailable/WinHttpReadData 循环和不阻止 WinHTTP 回调的任何时间段来正确清空 WinHTTP 缓冲区。

验证传输层安全性 (TLS)/安全套接字层 (SSL) 握手

作为安全最佳做法,游戏应对 TLS/SSL 握手执行额外的验证,并且仅使用 TLS 1.2。

WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 通知中执行其他验证。 在此通知中,必须调用 XNetworkingVerifyServerCertificate 函数,并传入从之前对 XNetworkingQuerySecurityInformationForUrlUtf16Async 的相应调用中检索到的 XNetworkingSecurityInformation 结构。 若证书链无效,则函数会失败。 应在回调完成前立即关闭 WinHTTP 句柄,以确保没有数据传输到/传输自损坏的服务器。

除了验证证书链之外,主机上的 Fiddler 功能还需要 XNetworkingVerifyServerCertificate 函数。

调试 WinHTTP

Fiddler 是一种用于查看和调试您的 WinHTTP 流量的有用工具。 为了使 Fiddler 能够捕获您的游戏流量,必须将 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAMEWINHTTP_NO_PROXY_BYPASS 标记传递到 WinHttpOpen。 还必须在 WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 通知回调中调用 XNetworkingVerifyServerCertificate

注意

HTTP 监视器不适用于 Microsoft 游戏开发工具包 (GDK) 游戏。

另请参阅

Windows HTTP 服务 (WinHTTP)

XSAPI C API 概述(安全链接)

XUser

在合作伙伴中心设置 Web 服务(NDA 主题)要求授权

Xbox One 主机上的 Fiddler

通信安全最佳做法概述(NDA 主题)要求授权