ACS Node.js 报错“服务不可用”? 真正原因与修复指南
2025-05-03 10:23:14
解谜 Azure Communication Services: "Our services aren't available right now" 报错背后
你在用 Azure Communication Services (ACS) 的 Node.js SDK 搞事情吗?比如想创建一个用户身份 (User Identity),然后获取用于 WebRTC 网络穿透的中继配置 (Relay Configuration),也就是那些 STUN/TURN 服务器信息 (ICE Servers)。
代码可能类似这样:
const { CommunicationIdentityClient } = require("@azure/communication-identity");
const { CommunicationRelayClient } = require("@azure/communication-network-traversal");
const main = async () => {
console.log("Azure Communication Services - 获取中继配置示例");
// 注意:这里应该放你自己的连接字符串
const connectionString = "你的 ACS 连接字符串";
// 实例化身份客户端
const identityClient = new CommunicationIdentityClient(connectionString);
let identityResponse;
try {
identityResponse = await identityClient.createUser();
console.log(`\n成功创建身份,ID: ${identityResponse.communicationUserId}`);
} catch (error) {
console.error("创建用户身份时出错:", error);
return; // 创建失败就没必要继续了
}
// 实例化中继客户端
const relayClient = new CommunicationRelayClient(connectionString);
console.log("准备获取中继配置...");
try {
// 使用创建的身份对象去获取配置
const config = await relayClient.getRelayConfiguration(identityResponse);
console.log("获取中继配置成功:", config);
if (config && config.iceServers) {
console.log("ICE 服务器列表:");
config.iceServers.forEach((server, index) => {
console.log(` Server ${index + 1}:`);
console.log(` URLs: ${server.urls.join(', ')}`);
console.log(` Username: ${server.username}`);
// Token 通常有时效性,且敏感,实际应用中按需处理
// console.log(` Credential (Token): ${server.credential}`);
console.log(` Route Type: ${server.routeType}`);
});
} else {
console.log("返回的配置中未包含 ICE 服务器信息。");
}
} catch (error) {
console.error("\n获取中继配置时遇到错误:");
// 尝试更详细地打印错误信息
if (error.response) {
console.error("HTTP 状态码:", error.response.status);
console.error("响应内容:", error.response.data || error.message); // 有可能是 HTML
} else {
console.error("错误详情:", error);
}
// 这里特别检查一下那个 HTML 错误
if (error.message && error.message.includes("<h2>Our services aren't available right now</h2>")) {
console.error("\n*** 看起来收到了 '服务暂时不可用' 的 HTML 页面,这通常不是真的服务中断,而是配置或认证问题。** *");
}
}
};
main().catch((error) => {
// 这个 catch 主要捕获 main 函数之外的、或者未被 try...catch 处理的 Promise 拒绝
console.error("\n顶层捕获到未处理错误:");
console.error(error);
});
结果,跑起来没拿到期望的 JSON 配置,反而收到了一个看着像服务中断页面的 HTML:
<h2>Our services aren't available right now</h2>
<p>We're working to restore all services as soon as possible. Please check back soon.</p>
看到这个,第一反应可能是:“完了,Azure 又挂了?” 但等了半天,或者问了同事,发现 ACS 服务本身运行正常。这就怪了。甚至可能像提问者那样,在社区找了一圈,试了用 axios
直接调 API,结果还是栽在同一个地方。
这到底是咋回事?
揪出“服务不可用”背后的真凶
这个 HTML 错误页面,具有一定的迷惑性。虽然它字面意思是“服务不可用”,但在 ACS 的场景下,尤其是通过 SDK 调用时遇到这个,大概率不是 Azure 服务真的全面瘫痪了 。更常见的原因,往往藏在你的代码或者配置里。
可能的原因包括:
-
连接字符串 (Connection String) 不对劲: 这是最常见的罪魁祸首。
- 复制粘贴时少了个字符、多了个空格?
Endpoint
地址写错了?或者指向了错误的区域?AccessKey
用错了?或者和Endpoint
不匹配?- 可能把别的 Azure 服务的连接字符串误用在这里了?
-
认证授权机制理解偏差:
CommunicationRelayClient
(以及多数 ACS 管理类操作的 SDK 客户端) 内部使用连接字符串里的 AccessKey 进行 HMAC 签名认证 ,向 Azure 服务证明“我是合法的服务调用者”。- 而像提问者在第二个
axios
尝试里那样,获取一个 用户 的accessToken
(Bearer Token
),然后用这个 token 去请求 服务级别 的中继配置,这是 用错了认证方式 。用户accessToken
是用来代表 终端用户 进行操作的(比如初始化呼叫 SDK、发送聊天消息),而不是用来代表你的 应用程序服务 去获取管理性配置的。错误类型的令牌自然会被拒绝,有时就返回这个通用的 HTML 错误页。
-
SDK 版本过时或存在 Bug: 虽然相对少见,但如果你使用的
@azure/communication-identity
或@azure/communication-network-traversal
包版本太老,可能存在已知问题,或者与当前 Azure 服务端行为不兼容。 -
直接 API 调用姿势错误 (针对 axios 尝试):
- 如上所述,认证方式错了(用了
Bearer token
)。 - 请求的 URL
/:issueRelayConfiguration?api-version=2022-03-01-preview
看上去就不太对劲,尤其是/:issueRelayConfiguration
这部分,可能不符合实际的 API 路径规范,或者使用的api-version
有问题或已过时。直接调用 REST API 需要精确匹配 Azure 的 API 文档,包括 URL、HTTP 方法、请求头、请求体和认证方式(通常是 HMAC 签名)。
- 如上所述,认证方式错了(用了
-
Azure 资源本身的问题:
- ACS 资源是否在 Azure Portal 中正确预配并处于活动状态?
- 网络策略、防火墙规则是否阻止了你的应用程序服务器访问 ACS 的
Endpoint
?(虽然这通常会导致连接超时或拒绝,而不是这个 HTML 页面)。
-
罕见情况:真的服务局部故障或维护: 不能完全排除,但如果是持续性问题,并且你反复检查了前几项都没问题,可以关注 Azure 服务状态页。
对症下药:解决方案逐个击破
别慌,咱们一步步来排查和解决。
方案一:回归初心,信赖官方 SDK (强烈推荐)
既然官方提供了 SDK,它就应该能搞定这事。问题很可能出在咱们给 SDK 的“料”——也就是连接字符串,或者环境配置上。
原理与作用:
SDK 封装了底层 HTTP 请求的细节,包括正确的 API 端点、请求格式以及关键的 HMAC 签名认证 。只要你提供正确的连接字符串,SDK 会自动用 AccessKey
生成签名并添加到请求头里,这才是获取中继配置等服务级操作所需的认证方式。
操作步骤:
-
死磕 Connection String:
- 重新获取: 登录 Azure Portal,找到你的 Azure Communication Services 资源。在左侧菜单选择“Keys”或“密钥”。
- 仔细复制: 那里会显示你的
Endpoint
和两个Access Keys
(主密钥/辅助密钥)。旁边通常直接提供完整的连接字符串。用那个“复制”按钮,确保完整复制,不要手动输入或修改。 - 检查格式: 连接字符串应该长这样:
endpoint=https://<你的资源名>.communication.azure.com/;accesskey=<你的访问密钥>
。注意分号;
和endpoint=
、accesskey=
这些。 - 环境变量/配置管理: 在生产或共享代码中,绝对不要 把连接字符串硬编码在代码里。使用环境变量、Azure Key Vault 或其他安全的配置管理工具来存储和读取它。检查你的配置加载逻辑是否正确无误。
-
确认 Azure 资源状态与区域:
- 在 Azure Portal 里确认你的 ACS 资源是“Active”或“正在运行”的状态。
- 确认连接字符串里的
Endpoint
URL 中的资源名<你的资源名>
和区域 (URL 通常隐含区域信息,比如*.uksouth.communication.azure.com
) 与你期望使用的资源一致。
-
更新 SDK 包到最新稳定版:
- 过时的包可能有 bug。执行以下命令更新:
npm update @azure/communication-identity @azure/communication-network-traversal # 或者,强制安装最新版 npm install @azure/communication-identity@latest @azure/communication-network-traversal@latest
- 理由: 新版本通常修复了已知问题,并能更好地兼容 Azure 服务端的更新。
- 过时的包可能有 bug。执行以下命令更新:
-
运行简化版 SDK 代码进行验证:
- 使用文章开头提供的、改进了错误处理的 SDK 代码。确保
connectionString
变量获取的是你刚刚核对过的、正确的值。 - 运行代码,注意观察控制台输出。如果还是报错,仔细看
console.error
打印出的详细信息,尤其是error.response
(如果存在) 里的状态码和内容,以及error.stack
堆栈信息。
- 使用文章开头提供的、改进了错误处理的 SDK 代码。确保
安全建议:
- 连接字符串是高度敏感的凭证,泄露它相当于给了别人完全控制你的 ACS 资源的权限(在这个 Key 的权限范围内)。务必使用安全的方式存储和管理,比如 Azure Key Vault。
- 考虑使用“辅助密钥” (Secondary Key) 并定期轮换密钥,增加安全性。
进阶使用技巧:
- 如果怀疑是网络问题,可以在运行代码的服务器上,尝试用
curl
或类似工具访问连接字符串中的Endpoint
地址(只访问域名,不需要 Key),看是否能通。例如:curl -v https://<你的资源名>.communication.azure.com
。这能帮你判断基础的网络连接和 DNS 解析是否正常。
方案二:诊断认证细节与环境 (如果方案一仍失败)
如果反复核对连接字符串并更新 SDK 后,问题依旧,我们需要更深入地看看。
原理与作用:
这一步旨在排除环境因素,并确认你对 ACS 认证机制的理解是准确的。获取中继配置是管理操作,必须用 AccessKey
认证,而不是用户 AccessToken
。
操作步骤:
-
确认认证方式:SDK 如何工作
- 再次明确:
CommunicationRelayClient
或CommunicationIdentityClient
在初始化时传入connectionString
,SDK 内部会解析出Endpoint
和AccessKey
。 - 发起
getRelayConfiguration
等请求时,SDK 会使用AccessKey
按照 Azure REST API 的要求计算 HMAC-SHA256 签名,并放入Authorization
请求头(以及其他必要的如x-ms-date
,x-ms-content-sha256
等头)。 - 这个过程对开发者是透明的。你不需要 像在第二个
axios
尝试中那样先去getToken
获取用户AccessToken
。那个 Token 是给客户端(比如你的 Web 前端或移动 App)使用的。
- 再次明确:
-
检查执行环境的网络:
- 确认你的服务器、本地开发环境或者 CI/CD 环境是否有防火墙或网络代理,它们可能阻止了到
*.communication.azure.com
的出站 HTTPS (端口 443) 连接。 - 如果是企业内网,可能需要配置代理服务器信息给 Node.js 或你的应用程序。
- 确认你的服务器、本地开发环境或者 CI/CD 环境是否有防火墙或网络代理,它们可能阻止了到
-
(谨慎操作) 尝试重新生成 Access Key:
- 如果极度怀疑
AccessKey
本身有问题(虽然可能性较低),可以考虑在 Azure Portal 中为你的 ACS 资源重新生成密钥 (Regenerate Key)。 - 重要警告: 重新生成一个密钥(比如主密钥)会立即使旧的主密钥失效。任何使用旧主密钥连接字符串的应用都会立刻停止工作。请务必先准备好更新所有相关应用的配置 ,或者先尝试使用“辅助密钥”;如果辅助密钥可用,可以生成新的主密钥,更新应用,然后再生成新的辅助密钥。
- 生成新密钥后,获取包含新密钥的完整连接字符串,替换掉你代码/配置中的旧字符串,然后再次运行方案一中的验证代码。
- 如果极度怀疑
安全建议:
- 密钥轮换是安全最佳实践,但需要规划执行,避免服务中断。
- 切勿在任何不安全的地方(如版本控制、客户端代码)暴露 Access Key 或完整连接字符串。
方案三:理解直接 API 调用的陷阱 (教育意义为主)
虽然强烈不推荐 绕过 SDK 直接调用 ACS 的 REST API 来获取中继配置(除非你有非常特殊且充分的理由,并且完全理解认证细节),但分析一下为什么之前的 axios
尝试会失败是有价值的。
错误分析:
用户第二个尝试中的 axios
代码主要错在:
- 认证方式错误: 如前所述,它使用了
Authorization: Bearer <user_access_token>
。获取中继配置需要的是基于AccessKey
的 HMAC 签名认证,其Authorization
头格式通常是HMAC-SHA256 SignedHeaders=<...> Signature=<...>
。 - API 端点和版本可疑:
/:issueRelayConfiguration
可能不是正确的路径,并且依赖一个preview
API 版本可能不稳定。
正确的(理论上的)直接调用:
要正确地直接调用,你需要:
- 从连接字符串解析出
Endpoint
和AccessKey
(是 AccessKey ,不是用户 Token!)。 - 根据 Azure REST API 文档找到获取中继配置的确切 API 端点、HTTP 方法 (可能是
POST
) 和当前推荐的稳定api-version
。 - 构造请求体(如果需要的话)。
- 获取当前 UTC 时间,格式化为 RFC1123 (
Mon, 02 Jan 2006 15:04:05 GMT
)。 - 计算请求体内容的 SHA256 哈希值(如果请求体非空)。
- 构造用于签名的字符串 (String-to-Sign),它通常包含 HTTP 谓词、URI、时间戳、主机名、内容哈希等信息。
- 使用
AccessKey
(先 Base64 解码) 作为密钥,计算签名字符串的 HMAC-SHA256 哈希值,然后将结果进行 Base64 编码。 - 将时间戳、内容哈希(如果需要)、签名等信息按规定格式放入
x-ms-date
、x-ms-content-sha256
、Authorization
等请求头中。 - 最后,用
axios
或其他 HTTP 客户端发送这个精心构造的请求。
结论:
看到上面这些步骤了吗?复杂、易错。这正是 SDK 存在的价值——它帮你处理了所有这些繁琐且关键的认证细节。
所以,最佳实践是:信任并用好官方 SDK。把精力放在确保连接字符串正确无误、SDK 是最新版本,以及执行环境网络通畅上。 那恼人的 "Our services aren't available right now" HTML 错误,多半就能迎刃而解了。