返回

ACS Node.js 报错“服务不可用”? 真正原因与修复指南

Ai

解谜 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 服务真的全面瘫痪了 。更常见的原因,往往藏在你的代码或者配置里。

可能的原因包括:

  1. 连接字符串 (Connection String) 不对劲: 这是最常见的罪魁祸首。

    • 复制粘贴时少了个字符、多了个空格?
    • Endpoint 地址写错了?或者指向了错误的区域?
    • AccessKey 用错了?或者和 Endpoint 不匹配?
    • 可能把别的 Azure 服务的连接字符串误用在这里了?
  2. 认证授权机制理解偏差:

    • CommunicationRelayClient (以及多数 ACS 管理类操作的 SDK 客户端) 内部使用连接字符串里的 AccessKey 进行 HMAC 签名认证 ,向 Azure 服务证明“我是合法的服务调用者”。
    • 而像提问者在第二个 axios 尝试里那样,获取一个 用户accessToken (Bearer Token),然后用这个 token 去请求 服务级别 的中继配置,这是 用错了认证方式 。用户 accessToken 是用来代表 终端用户 进行操作的(比如初始化呼叫 SDK、发送聊天消息),而不是用来代表你的 应用程序服务 去获取管理性配置的。错误类型的令牌自然会被拒绝,有时就返回这个通用的 HTML 错误页。
  3. SDK 版本过时或存在 Bug: 虽然相对少见,但如果你使用的 @azure/communication-identity@azure/communication-network-traversal 包版本太老,可能存在已知问题,或者与当前 Azure 服务端行为不兼容。

  4. 直接 API 调用姿势错误 (针对 axios 尝试):

    • 如上所述,认证方式错了(用了 Bearer token)。
    • 请求的 URL /:issueRelayConfiguration?api-version=2022-03-01-preview 看上去就不太对劲,尤其是 /:issueRelayConfiguration 这部分,可能不符合实际的 API 路径规范,或者使用的 api-version 有问题或已过时。直接调用 REST API 需要精确匹配 Azure 的 API 文档,包括 URL、HTTP 方法、请求头、请求体和认证方式(通常是 HMAC 签名)。
  5. Azure 资源本身的问题:

    • ACS 资源是否在 Azure Portal 中正确预配并处于活动状态?
    • 网络策略、防火墙规则是否阻止了你的应用程序服务器访问 ACS 的 Endpoint?(虽然这通常会导致连接超时或拒绝,而不是这个 HTML 页面)。
  6. 罕见情况:真的服务局部故障或维护: 不能完全排除,但如果是持续性问题,并且你反复检查了前几项都没问题,可以关注 Azure 服务状态页。

对症下药:解决方案逐个击破

别慌,咱们一步步来排查和解决。

方案一:回归初心,信赖官方 SDK (强烈推荐)

既然官方提供了 SDK,它就应该能搞定这事。问题很可能出在咱们给 SDK 的“料”——也就是连接字符串,或者环境配置上。

原理与作用:

SDK 封装了底层 HTTP 请求的细节,包括正确的 API 端点、请求格式以及关键的 HMAC 签名认证 。只要你提供正确的连接字符串,SDK 会自动用 AccessKey 生成签名并添加到请求头里,这才是获取中继配置等服务级操作所需的认证方式。

操作步骤:

  1. 死磕 Connection String:

    • 重新获取: 登录 Azure Portal,找到你的 Azure Communication Services 资源。在左侧菜单选择“Keys”或“密钥”。
    • 仔细复制: 那里会显示你的 Endpoint 和两个 Access Keys (主密钥/辅助密钥)。旁边通常直接提供完整的连接字符串。用那个“复制”按钮,确保完整复制,不要手动输入或修改。
    • 检查格式: 连接字符串应该长这样:endpoint=https://<你的资源名>.communication.azure.com/;accesskey=<你的访问密钥>。注意分号 ;endpoint=accesskey= 这些。
    • 环境变量/配置管理: 在生产或共享代码中,绝对不要 把连接字符串硬编码在代码里。使用环境变量、Azure Key Vault 或其他安全的配置管理工具来存储和读取它。检查你的配置加载逻辑是否正确无误。
  2. 确认 Azure 资源状态与区域:

    • 在 Azure Portal 里确认你的 ACS 资源是“Active”或“正在运行”的状态。
    • 确认连接字符串里的 Endpoint URL 中的资源名 <你的资源名> 和区域 (URL 通常隐含区域信息,比如 *.uksouth.communication.azure.com) 与你期望使用的资源一致。
  3. 更新 SDK 包到最新稳定版:

    • 过时的包可能有 bug。执行以下命令更新:
      npm update @azure/communication-identity @azure/communication-network-traversal
      # 或者,强制安装最新版
      npm install @azure/communication-identity@latest @azure/communication-network-traversal@latest
      
    • 理由: 新版本通常修复了已知问题,并能更好地兼容 Azure 服务端的更新。
  4. 运行简化版 SDK 代码进行验证:

    • 使用文章开头提供的、改进了错误处理的 SDK 代码。确保 connectionString 变量获取的是你刚刚核对过的、正确的值。
    • 运行代码,注意观察控制台输出。如果还是报错,仔细看 console.error 打印出的详细信息,尤其是 error.response (如果存在) 里的状态码和内容,以及 error.stack 堆栈信息。

安全建议:

  • 连接字符串是高度敏感的凭证,泄露它相当于给了别人完全控制你的 ACS 资源的权限(在这个 Key 的权限范围内)。务必使用安全的方式存储和管理,比如 Azure Key Vault。
  • 考虑使用“辅助密钥” (Secondary Key) 并定期轮换密钥,增加安全性。

进阶使用技巧:

  • 如果怀疑是网络问题,可以在运行代码的服务器上,尝试用 curl 或类似工具访问连接字符串中的 Endpoint 地址(只访问域名,不需要 Key),看是否能通。例如:curl -v https://<你的资源名>.communication.azure.com。这能帮你判断基础的网络连接和 DNS 解析是否正常。

方案二:诊断认证细节与环境 (如果方案一仍失败)

如果反复核对连接字符串并更新 SDK 后,问题依旧,我们需要更深入地看看。

原理与作用:

这一步旨在排除环境因素,并确认你对 ACS 认证机制的理解是准确的。获取中继配置是管理操作,必须用 AccessKey 认证,而不是用户 AccessToken

操作步骤:

  1. 确认认证方式:SDK 如何工作

    • 再次明确:CommunicationRelayClientCommunicationIdentityClient 在初始化时传入 connectionString,SDK 内部会解析出 EndpointAccessKey
    • 发起 getRelayConfiguration 等请求时,SDK 会使用 AccessKey 按照 Azure REST API 的要求计算 HMAC-SHA256 签名,并放入 Authorization 请求头(以及其他必要的如 x-ms-date, x-ms-content-sha256 等头)。
    • 这个过程对开发者是透明的。你不需要 像在第二个 axios 尝试中那样先去 getToken 获取用户 AccessToken。那个 Token 是给客户端(比如你的 Web 前端或移动 App)使用的。
  2. 检查执行环境的网络:

    • 确认你的服务器、本地开发环境或者 CI/CD 环境是否有防火墙或网络代理,它们可能阻止了到 *.communication.azure.com 的出站 HTTPS (端口 443) 连接。
    • 如果是企业内网,可能需要配置代理服务器信息给 Node.js 或你的应用程序。
  3. (谨慎操作) 尝试重新生成 Access Key:

    • 如果极度怀疑 AccessKey 本身有问题(虽然可能性较低),可以考虑在 Azure Portal 中为你的 ACS 资源重新生成密钥 (Regenerate Key)。
    • 重要警告: 重新生成一个密钥(比如主密钥)会立即使旧的主密钥失效。任何使用旧主密钥连接字符串的应用都会立刻停止工作。请务必先准备好更新所有相关应用的配置 ,或者先尝试使用“辅助密钥”;如果辅助密钥可用,可以生成新的主密钥,更新应用,然后再生成新的辅助密钥。
    • 生成新密钥后,获取包含新密钥的完整连接字符串,替换掉你代码/配置中的旧字符串,然后再次运行方案一中的验证代码。

安全建议:

  • 密钥轮换是安全最佳实践,但需要规划执行,避免服务中断。
  • 切勿在任何不安全的地方(如版本控制、客户端代码)暴露 Access Key 或完整连接字符串。

方案三:理解直接 API 调用的陷阱 (教育意义为主)

虽然强烈不推荐 绕过 SDK 直接调用 ACS 的 REST API 来获取中继配置(除非你有非常特殊且充分的理由,并且完全理解认证细节),但分析一下为什么之前的 axios 尝试会失败是有价值的。

错误分析:

用户第二个尝试中的 axios 代码主要错在:

  1. 认证方式错误: 如前所述,它使用了 Authorization: Bearer <user_access_token>。获取中继配置需要的是基于 AccessKey 的 HMAC 签名认证,其 Authorization 头格式通常是 HMAC-SHA256 SignedHeaders=<...> Signature=<...>
  2. API 端点和版本可疑: /:issueRelayConfiguration 可能不是正确的路径,并且依赖一个 preview API 版本可能不稳定。

正确的(理论上的)直接调用:

要正确地直接调用,你需要:

  1. 从连接字符串解析出 EndpointAccessKey (是 AccessKey ,不是用户 Token!)。
  2. 根据 Azure REST API 文档找到获取中继配置的确切 API 端点、HTTP 方法 (可能是 POST) 和当前推荐的稳定 api-version
  3. 构造请求体(如果需要的话)。
  4. 获取当前 UTC 时间,格式化为 RFC1123 (Mon, 02 Jan 2006 15:04:05 GMT)。
  5. 计算请求体内容的 SHA256 哈希值(如果请求体非空)。
  6. 构造用于签名的字符串 (String-to-Sign),它通常包含 HTTP 谓词、URI、时间戳、主机名、内容哈希等信息。
  7. 使用 AccessKey (先 Base64 解码) 作为密钥,计算签名字符串的 HMAC-SHA256 哈希值,然后将结果进行 Base64 编码。
  8. 将时间戳、内容哈希(如果需要)、签名等信息按规定格式放入 x-ms-datex-ms-content-sha256Authorization 等请求头中。
  9. 最后,用 axios 或其他 HTTP 客户端发送这个精心构造的请求。

结论:

看到上面这些步骤了吗?复杂、易错。这正是 SDK 存在的价值——它帮你处理了所有这些繁琐且关键的认证细节。

所以,最佳实践是:信任并用好官方 SDK。把精力放在确保连接字符串正确无误、SDK 是最新版本,以及执行环境网络通畅上。 那恼人的 "Our services aren't available right now" HTML 错误,多半就能迎刃而解了。