面向 https.onCall 的协议规范

Cloud Functions 的 https.onCall 触发器是一个采用特定格式的请求和响应的 HTTPS 触发器。本部分介绍了客户端 SDK 实现 API 时使用的 HTTPS 请求和响应的格式规范。如果使用 Android SDK、Apple 平台 SDK 或 Web SDK 无法满足您的需求,这些信息可能对您会很有用。

请求格式:标头

向 Callable 触发器端点发送的 HTTP 请求必须是带有以下标头的 POST

  • 必需:Content-Type: application/json
    • 允许使用可选的 ; charset=utf-8
  • 可选:Authorization: Bearer <token>
    • 发出请求的已登录用户的 Firebase Authentication 用户 ID 令牌。后端会自动验证此令牌并在处理程序的 context 中提供此令牌。如果此令牌无效,则请求会被拒绝。
  • 可选:Firebase-Instance-ID-Token: <iid>
    • 来自 Firebase 客户端 SDK 的 FCM 注册令牌。这必须是一个字符串。可以在处理程序的 context 中找到此令牌。它用于定位推送通知。
  • 可选:X-Firebase-AppCheck: <token>
    • 由发出请求的客户端应用提供的 Firebase App Check 令牌。后端会自动验证此令牌并对其进行解码,从而在处理程序的 context 中注入 appId。如果令牌无法验证,请求将被拒绝。(适用于 SDK 3.14.0 及更高版本)

如果请求中还包含任何其他标头,则会被拒绝(如下面的响应文档中所述)。

注意:在 JavaScript 客户端中,这些请求会触发 CORS OPTIONS 预检,这是因为:

  • 不允许使用 application/json。必须为 text/plainapplication/x-www-form-urlencoded
  • Authorization 标头不是 CORS 安全名单中的请求标头
  • 同样,其他标头也是不允许使用的。

Callable 触发器会自动处理这些 OPTIONS 请求。

请求正文

HTTP 请求的正文应该是具有以下任何字段的 JSON 对象:

  • 必需:data- 传递给函数的参数。这可以是任何有效的 JSON 值。该参数会根据如下所述的序列化格式自动解码为本机 JavaScript 类型。

如果请求中有任何其他字段,则后端会将该请求视为格式错误,并拒绝该请求。

响应格式:状态代码

以下几种情况可能导致响应中的错误具有不同的 HTTP 状态代码和字符串状态代码。

  1. 如果在调用 client 触发器之前发生 HTTP 错误,则响应不会作为客户端函数进行处理。例如,如果客户端尝试调用不存在的函数,则它会收到 404 Not Found 响应。

  2. 如果调用了客户端触发器,但请求的格式不正确(例如不是 JSON、包含无效字段或缺少 data 字段),则请求将被拒绝并返回 400 Bad Request,错误代码为 INVALID_ARGUMENT

  3. 如果请求中提供的身份验证令牌无效,则请求将被拒绝并返回 401 Unauthorized,错误代码为 UNAUTHENTICATED

  4. 如果请求中提供的 FCM 注册令牌无效,那么这种行为属于未定义的行为。系统并不会针对每个请求检查令牌,但使用这种令牌通过 FCM 发送推送通知时除外。

  5. 如果调用了 Callable 触发器,但调用失败并产生未处理的异常或返回失败的 Promise,则请求将被拒绝并返回 500 Internal Server Error,错误代码为 INTERNAL。这可以避免编码错误意外地向最终用户显示。

  6. 如果使用为 Callable 函数提供的 API 调用了 Callable 并返回显式错误条件,则请求会失败。系统会根据错误状态与 HTTP 状态的官方映射(如 code.proto 中所定义)返回 HTTP 状态代码。返回的具体错误代码、消息和详细信息会在响应正文中进行编码(如下所述)。这意味着如果函数返回状态为 OK 的显式错误,则响应的状态为 200 OK,但 error 字段会在响应中进行设置。

  7. 如果客户端触发器成功,则响应状态为 200 OK

响应格式:标头

响应具有以下标头:

  • Content-Type: application/json
  • 允许使用可选的 ; charset=utf-8

响应正文

来自客户端端点的响应始终是 JSON 对象。它至少包含 resulterror 以及任何可选字段。如果响应不是 JSON 对象,或者不包含 dataerror,则客户端 SDK 应将请求视为失败并返回 Google 错误代码 INTERNAL (13)

  • error - 如果此字段存在,则请求将被视为失败,无论 HTTP 状态代码为何或 data 是否也存在。此字段的值应该是一个 JSON 对象(采用为错误制定的标准 Google Cloud HTTP 映射格式),并且包含 statusmessage 及(可选)details字段。不应包含 code 字段。如果 status 字段未设置或是无效值,则客户端应根据 code.proto 将状态视为 INTERNAL。如果存在 details,则其包含在客户端 SDK 中随附于错误的用户信息中(若有)。
    注意:这里的 details 字段是用户提供的值。它未必是像在 Google Status 格式中那样由原型键控的值列表。
  • result - 函数返回的值。这可以是任何有效的 JSON 值。firebase-functions SDK 会自动将用户返回的值编码为这种 JSON 格式。客户端 SDK 会根据下文所述的序列化格式自动将这些参数解码为本机类型。

如果存在其他字段,则应忽略它们。

序列化

无论是请求还是响应,任意数据载荷的序列化格式都是相同的。

为保证平台一致性,这些数据载荷会像 proto3 协议缓冲区中 Any 字段的值一样,使用标准 JSON 映射进行 JSON 编码。简单类型(如 nullintdoublestring)的值将直接编码,不会包含它们的显式类型。因此,floatdouble 的编码方式相同,并且您可能不知道在调用的另一端收到了哪一个。对于非 JSON 本机类型,将使用相应值的类型化 proto3 编码。如需了解详情,请参阅任何 JSON 编码的文档

允许使用以下类型:

  • null - null
  • 整数(有符号或无符号,最多 32 位)- 例如 3-30
  • 浮点数 - 例如 3.14
  • 双精度浮点数 - 例如 3.14
  • 布尔值 - true or false
  • 字符串 - 例如 "hello world"
  • map<string, any=""> - 如 {"x": 3}</string,>
  • 列表 - 例如 [1, 2, 3]
  • 长整型(有符号或无符号,最多 64 位)- [详见下文]

不支持 floatdoubleNaNInfinity 值。

请注意,long是 JSON 中通常不允许的特殊类型,但 proto3 规范涵盖这种类型。例如,这些类型的编码方式如下:

长整型

{
    '@type': 'type.googleapis.com/google.protobuf.Int64Value',
    'value': '-123456789123456'
}

无符号长整型

{
    '@type': 'type.googleapis.com/google.protobuf.UInt64Value',
    'value': '123456789123456'
}

一般情况下,@type 键应视为已预留,并且不应该用于传入的映射。

由于没有将相应类型指定为简单类型,因此某些值会在在线传递后改变类型。传入的 float 将变为 doubleshort 变为 int,以此类推。在 Android 中,列表值支持 ListJSONArray。在这些情况下,传入 JSONArray 将生成一个 List

如果包含未知 @type 字段的映射被去序列化,则会被保留为映射。这样,开发者可以将包含新类型的字段添加到返回值中,而不会使较旧的客户端发生服务中断。

代码示例

此部分中的示例说明如何对以下内容编码:

  • callable.call Swift 示例
  • 调用的成功响应
  • 调用的失败响应

Callable.call Swift 编码示例

callable.call([
    "aString": "some string",
    "anInt": 57,
    "aFloat": 1.23,
    "aLong": -123456789123456 as Int64
])

请求标头:

Method: POST
Content-Type: application/json; charset=utf-8
Authorization: Bearer some-auth-token
Firebase-Instance-ID-Token: some-iid-token

请求正文:

{
    "data": {
        "aString": "some string",
        "anInt": 57,
        "aFloat": 1.23,
        "aLong": {
            "@type": "type.googleapis.com/google.protobuf.Int64Value",
            "value": "-123456789123456"
        }
    }
}

响应编码

return {
    "aString": "some string",
    "anInt": 57,
    "aFloat": 1.23
};

成功响应标头:

200 OK
Content-Type: application/json; charset=utf-8

成功响应正文:

{
    "response": {
        "aString": "some string",
        "anInt": 57,
        "aFloat": 1.23
    }
}

失败响应编码

throw new HttpsError("unauthenticated", "Request had invalid credentials.", {
  "some-key": "some-value"
});

失败的响应标头:

401 UNAUTHENTICATED
Content-Type: application/json; charset=utf-8

失败的响应正文:

{
    "error": {
        "message": "Request had invalid credentials.",
        "status": "UNAUTHENTICATED",
        "details": {
            "some-key": "some-value"
        }
    }
}