Admin SDK 错误分为两类:
- 编程错误:这些错误是指
用户应用出现这些错误主要是因为 SDK 使用不当
(例如,将
null
传递给不接受null
值的方法);以及 Firebase 项目或 SDK 级别的其他配置错误(缺少 凭据、项目 ID 字符串不正确等)。 - API 错误: 包括 SDK 实现中发生的各种可恢复的错误, 源自 Firebase 后端服务的错误以及其他暂时性的错误 错误(如超时)。
对于编程错误,Admin SDK 会抛出一个 相关平台
- Java:抛出
IllegalArgumentException
、NullPointerException
实例 或类似的内置运行时错误类型。 - Python:引发
ValueError
、TypeError
或其他内置错误类型的实例。 - Go:返回一个常规错误。
- .NET:抛出
ArgumentException
、ArgumentNullException
或 类似的内置错误类型。
在大多数情况下,您不应明确处理编程错误。相反, 您应修正代码和配置,完全避免编程错误。 请考虑以下 Java 代码段:
String uid = getUserInput();
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
如果 getUserInput()
方法返回 null
或空字符串,则
FirebaseAuth.getUser()
API 会抛出 IllegalArgumentException
。而不是
那么你就可以通过确保
getUserInput()
方法绝不会返回无效的 UID 字符串。如果
请在您自己的代码中实现必要的参数检查,如下所示:
String uid = getUserInput();
if (Strings.isNullOrEmpty(uid)) {
log.warn("UID must not be null or empty");
return;
}
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
原则上,切勿在遇到编程错误时重试。支持快速失败 通常最适合处理编程错误的语义, 会在开发过程中出现编程错误和配置错误, 可以及时解决在这种情况下,快速失败可能意味着 传播到应用中的全局错误处理程序,或者只记录错误 出于审核目的,然后终止当前执行流程 (应用应该不会崩溃)。一般来说,如果遇到错误, 处理编程语言和应用的最佳实践 框架。仅凭这一点通常足以正确处理这一类 错误。
通常,您的大部分错误处理工作将集中在处理 API 错误。其中一些错误是可以恢复的,例如 一项服务暂时不可用,我们甚至可能在 正常的程序执行流程,例如检测无效或过期的 ID 令牌。 本指南的其余部分概述了 Admin SDK 如何表示此类 API 错误, 以及可用于处理这些事件的各种选项
API 错误的结构
API 错误由以下部分组成:
- 错误代码
- 错误消息
- 服务错误代码(可选)
- HTTP 响应(可选)
每个 API 错误都必须包含错误代码和错误消息。 某些 API 错误还包含该 API 特有的服务错误代码 生成错误的模板。例如,Firebase 身份验证生成的一些错误 API 包含 Firebase Auth 特有的服务错误代码。如果错误 是来自后端服务的 HTTP 错误响应的结果, 还包含相应的 HTTP 响应。这可用于检查 原始响应的准确标头和内容,这对于 调试、记录或实现更复杂的错误处理逻辑。
除 Node.js 之外的所有 Admin SDK 实现都提供了允许访问 以上 API 错误的组成部分。
错误类型和 API(按语言)
Java
在 Java 中,所有 API 错误都会扩展 FirebaseException
类。您可以访问
错误代码、错误消息和来自此基类的可选 HTTP 响应。
public class FirebaseException extends Exception {
@NonNull
public ErrorCode getErrorCode() {
// ...
}
@NonNull
public String getMessage() {
// ...
}
@Nullable
public IncomingHttpResponse getHttpResponse() {
// ...
}
}
公开服务错误代码的 API 提供特定于 API 的子类,
FirebaseException
。例如,FirebaseAuth
API 中的所有公共方法
声明抛出 FirebaseAuthException
的实例。您可以访问
服务错误代码。
public class FirebaseAuthException extends FirebaseException {
@Nullable
public AuthErrorCode getAuthErrorCode() {
// ...
}
}
Python
在 Python 中,所有 API 错误都会扩展 exceptions.FirebaseError
类。您可以查看错误代码、错误消息和可选的
响应。
class FirebaseError(Exception):
@property
def code(self):
# ...
@property
def message(self):
# ...
@property
def http_response(self):
# ...
此外,Python Admin SDK 为每个错误代码提供单独的派生类。 我们将其称为平台错误类。
class InvalidArgumentError(FirebaseError):
# ...
class NotFoundError(FirebaseError):
# ...
您可以在代码中捕获 FirebaseError
并检查其 code
,或者
针对平台错误类执行 isinstance()
检查。您也可以编写
代码来直接捕获特定的平台错误类型。后一种方法是
可能导致错误处理代码可读性更强。
公开服务错误代码的 API 提供特定于 API 的平台子类
错误类。例如,auth
模块中的所有公共方法都可能会抛出
API 特有的错误类型,例如 auth.UserNotFoundError
和
auth.ExpiredIdTokenError
。
class UserNotFoundError(exceptions.NotFoundError):
# …
class ExpiredIdTokenError(exceptions.InvalidArgumentError):
# ...
Go
Go Admin SDK 提供了一个 errorutils
软件包,其中包含一系列
用于测试错误代码的函数。
package errorutils
func IsInvalidArgument(err error) bool {
// ...
}
func IsNotFound(err error) bool {
// ...
}
错误消息只是由Error()
错误。可选的 HTTP 响应可通过调用
errorutils.HTTPResponse()
函数,该函数会返回 *http.Response
。
将 nil
或任何其他错误值传递给错误检查是安全的
函数。errorutils
如果输入参数,则返回 true
实际包含相关的错误代码,并针对所有内容返回 false
其他。HTTPResponse()
函数具有类似的行为,但它会返回
nil
,而非 false
。
公开服务错误代码的 API 提供特定于 API 的错误检查
函数。例如,auth
软件包
提供函数 IsUserNotFound()
和 IsExpiredIDTokenError()
。
.NET
在 .NET 中,所有 API 错误都会扩展 FirebaseException
类。您可以访问
平台错误代码、错误消息以及来自此基础服务的可选 HTTP 响应
类。
public class FirebaseException : Exception {
public ErrorCode ErrorCode { get; }
public String Message { get; }
public HttpResponseMessage HttpResponse { get; }
}
公开服务错误代码的 API 提供特定于 API 的子类,
FirebaseException
。例如,FirebaseAuth
API 中的所有公共方法
声明抛出 FirebaseAuthException
实例。
您可以访问
服务错误代码。
public class FirebaseAuthException : FirebaseException {
public AuthErrorCode AuthErrorCode { get; }
}
平台错误代码
错误代码在所有 Firebase 和 Google Cloud Platform 服务中都是通用的。 下表列出了所有可能的平台错误代码。这是一个 稳定版,并且应该会在很长一段时间内保持不变。
INVALID_ARGUMENT | 客户端指定了无效参数。 |
FAILED_PRECONDITION | 请求无法在当前系统状态下执行,例如删除非空目录。 |
OUT_OF_RANGE | 客户端指定的范围无效。 |
未经过身份验证 | 由于 OAuth 令牌缺失、无效或过期,请求未通过身份验证。 |
PERMISSION_DENIED | 客户端权限不足。发生这种情况可能是因为 OAuth 令牌没有正确的范围、客户端没有权限或者尚未为客户端项目启用 API。 |
NOT_FOUND | 找不到指定的资源,或者请求因未公开的原因(例如白名单)而被拒绝。 |
冲突 | 并发冲突,例如读取-修改-写入冲突。仅由少数旧版服务使用。大多数服务使用 ABORTED 或 ALREADY_EXISTS 来代替它。请参阅具体服务的文档,了解要在代码中处理哪个服务。 |
中止 | 并发冲突,例如读取-修改-写入冲突。 |
ALREADY_EXISTS | 客户端尝试创建的资源已存在。 |
RESOURCE_EXHAUSTED | 资源配额不足或达到速率限制。 |
已取消 | 客户端取消了请求。 |
数据损失 | 出现不可恢复的数据丢失或数据损坏。客户端应向用户报告错误。 |
未知 | 出现未知的服务器错误。通常是服务器错误。
此错误代码也被指定给本地响应解析(解组)错误,以及不易诊断的各种低级 I/O 错误。 |
INTERNAL | 内部服务器错误。通常是服务器错误。 |
无法观看 | 服务不可用。通常,服务器暂时关闭。
此错误代码也会分配给本地网络错误(连接被拒绝,没有通向主机的路由)。 |
DEADLINE_EXCEEDED | 超出请求时限。仅当调用方设置的截止时间短于目标 API 的默认截止时间(即请求的截止时间不足以让服务器处理请求)且请求未在截止时间内完成时,才会发生这种情况。
此错误代码也会分配给本地连接和读取超时。 |
大多数 API 只能导致上述错误代码的一部分。无论如何 执行上述操作时,不应明确处理所有这些错误代码 实现错误处理程序大多数应用只关心 1-2 个特定的错误代码,并将其他所有内容视为不可恢复的通用 失败。
特定服务的错误代码
Firebase Auth
CERTIFICATE_FETCH_FAILED 份 | 未能提取验证 JWT(ID 令牌或会话 Cookie)所需的公钥证书。 |
EMAIL_ALREADY_EXISTS | 已存在使用所提供的电子邮件地址的用户。 |
EXPIRED_ID_TOKEN | 为 verifyIdToken() 指定的 ID 令牌已过期。
|
EXPIRED_SESSION_COOKIE | 为 verifySessionCookie() 指定的会话 Cookie 已过期。
|
INVALID_DYNAMIC_LINK_DOMAIN | 提供的动态链接网域没有针对当前项目配置或授权。与电子邮件操作链接 API 相关。 |
INVALID_ID_TOKEN | 为 verifyIdToken() 指定的 ID 令牌无效。
|
“INVALID_SESSION_COOKIE” | 为 verifySessionCookie() 指定的会话 Cookie 无效。
|
PHONE_NUMBER_ALREADY_EXISTS | 已有用户使用所提供的电话号码。 |
REVOKED_ID_TOKEN | 撤消了为 verifyIdToken() 指定的 ID 令牌。
|
REVOKED_SESSION_COOKIE | 为 verifySessionCookie() 指定的会话 Cookie 已过期。
|
UNAUTHORIZED_CONTINUE_网址 | 接续网址的网域未列入白名单。与电子邮件操作链接 API 相关。 |
USER_NOT_FOUND | 未找到与指定标识符对应的用户记录。 |
Firebase Cloud Messaging
THIRD_PARTY_AUTH_ERROR | APNs 证书或 Web 推送身份验证 API 密钥无效或缺失。 |
INVALID_ARGUMENT | 请求中指定的一个或多个参数无效。 |
INTERNAL | 内部服务器错误。 |
超出 QUOTA_EXCEEDED | 消息目标的发送次数已超出上限。 |
SENDER_ID_MISMATCH | 经过身份验证的发送者 ID 与注册令牌的发送者 ID 不同。这通常意味着发送者和目标注册令牌不在同一个 Firebase 项目中。 |
无法观看 | Cloud Messaging服务暂时无法使用。 |
未报名 | 应用实例已从 FCM 中取消注册。这通常意味着使用的设备注册令牌已失效,必须使用新的令牌。 |
自动重试
在显示某些错误之前,Admin SDK 会自动重试这些错误 。一般情况下,系统会以透明的方式重试以下类型的错误:
- 由 HTTP 503(服务不可用)响应导致的所有 API 错误。
- 由 HTTP 500(内部服务器错误)响应导致的某些 API 错误。
- 大多数低级 I/O 错误(连接被拒绝、连接重置等)。
对于上述每个错误,SDK 都会重试最多 5 次(原始尝试) + 4 次重试)。您可以实现自己的重试 机制,但这通常不是 必填字段。
重试 - 支持服务
Admin SDK 的 Go 和 .NET 实现支持
处理 HTTP Retry-After
标头。也就是说,如果
如果后端服务器包含标准 Retry-After
标头,SDK 将
遵循以下原则:重试时,只要指定的等待时长不是很长时间
长。如果 Retry-After
标头指示的等待时长很长,SDK
将绕过重试并抛出相应的 API 错误。
Python Admin SDK 目前不支持 Retry-After
标头,并且
仅支持简单的指数退避算法。
API 错误处理示例
实现通用错误处理程序
在大多数情况下,您需要的是捕获宽泛的 错误范围,以防止程序流因 API 错误。此类错误处理程序通常只记录错误以进行审核, 或者针对遇到的所有 API 调用其他默认的错误处理例程 错误。他们不一定关注不同的错误代码或 以及可能导致该错误的原因。
Java
try {
FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
System.err.println("Failed to verify ID token: " + ex.getMessage());
}
Python
try:
token = auth.verify_id_token(idToken)
perform_privileged_pperation(token.uid)
except exceptions.FirebaseError as ex:
print(f'Failed to verify ID token: {ex}')
Go
token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
log.Printf("Failed to verify ID token: %v", err)
return
}
performPrivilegedOperation(token)
.Net
try
{
var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}
检查错误代码
在某些情况下,您可能希望检查确切的错误代码,并调用 不同的情境感知错误处理例程。在以下示例中,我们 具有一个错误处理程序,可根据 服务错误代码。
Java
try {
FirebaseToken token = FirebaseAuth.getInstance().verifyIdToken(idToken);
performPrivilegedOperation(token.getUid());
} catch (FirebaseAuthException ex) {
if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_EXPIRED) {
System.err.println("ID token has expired");
} else if (ex.getAuthErrorCode() == AuthErrorCode.ID_TOKEN_INVALID) {
System.err.println("ID token is malformed or invalid");
} else {
System.err.println("Failed to verify ID token: " + ex.getMessage());
}
}
Python
try:
token = auth.verify_id_token(idToken)
perform_privileged_operation(token.uid)
except auth.ExpiredIdTokenError:
print('ID token has expired')
except auth.InvalidIdTokenError:
print('ID token is malformed or invalid')
except exceptions.FirebaseError as ex:
print(f'Failed to verify ID token: {ex}')
Go
token, err := client.VerifyIDToken(ctx, idToken)
if auth.IsIDTokenExpired(err) {
log.Print("ID token has expired")
return
}
if auth.IsIDTokenInvalid(err) {
log.Print("ID token is malformed or invalid")
return
}
if err != nil {
log.Printf("Failed to verify ID token: %v", err)
return
}
performPrivilegedOperation(token)
.Net
try
{
var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
if (ex.AuthErrorCode == AuthErrorCode.ExpiredIdToken)
{
Console.WriteLine("ID token has expired");
}
else if (ex.AuthErrorCode == AuthErrorCode.InvalidIdToken)
{
Console.WriteLine("ID token is malformed or invalid");
}
else
{
Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}
}
下面是我们同时检查顶级错误代码和服务错误代码的另一个示例:
Java
try {
FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
if (ex.getMessagingErrorCode() == MessagingErrorCode.UNREGISTERED) {
System.err.println("App instance has been unregistered");
removeTokenFromDatabase();
} else if (ex.getErrorCode() == ErrorCode.Unavailable) {
System.err.println("FCM service is temporarily unavailable");
scheduleForRetryInAnHour();
} else {
System.err.println("Failed to send notification: " + ex.getMessage());
}
}
Python
try:
messaging.send(create_my_message())
except messaging.UnregisteredError:
print('App instance has been unregistered')
remove_token_from_database()
except exceptions.UnavailableError:
print('FCM service is temporarily unavailable')
schedule_for_retry_in_an_hour()
except exceptions.FirebaseError as ex:
print(f'Failed to send notification: {ex}')
Go
_, err := client.Send(ctx, createMyMessage())
if messaging.IsUnregistered(err) {
log.Print("App instance has been unregistered")
removeTokenFromDatabase()
return
}
if errorutils.IsUnavailable(err) {
log.Print("FCM service is temporarily unavailable")
scheduleForRetryInAnHour()
return
}
if err != nil {
log.Printf("Failed to send notification: %v", err)
return
}
.Net
try
{
await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
if (ex.MessagingErrorCode == MessagingErrorCode.UNREGISTERED)
{
Console.WriteLine("App instance has been unregistered");
removeTokenFromDatabase();
}
else if (ex.ErrorCode == ErrorCode.Unavailable)
{
Console.WriteLine("FCM service is temporarily unavailable");
scheduleForRetryInAnHour();
}
else
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
}
访问 HTTP 响应
在极少数情况下,您可能需要检查 对后端服务执行一些错误处理操作。Admin SDK 公开这些错误响应的标头和内容。响应 内容通常以字符串或原始字节序列的形式返回,且可以是 解析为任何必要的目标格式。
Java
try {
FirebaseMessaging.getInstance().send(createMyMessage());
} catch (FirebaseMessagingException ex){
IncomingHttpResponse response = ex.getHttpResponse();
if (response != null) {
System.err.println("FCM service responded with HTTP " + response.getStatusCode());
Map<String, Object> headers = response.getHeaders();
for (Map.Entry<String, Object> entry : headers.entrySet()) {
System.err.println(">>> " + entry.getKey() + ": " + entry.getValue());
}
System.err.println(">>>");
System.err.println(">>> " + response.getContent());
}
}
Python
try:
messaging.send(create_my_message())
except exceptions.FirebaseError as ex:
response = ex.http_response
if response is not None:
print(f'FCM service responded with HTTP {response.status_code}')
for key, value in response.headers.items():
print(f'>>> {key}: {value}')
print('>>>')
print(f'>>> {response.content}')
Go
_, err := client.Send(ctx, createMyMessage())
if resp := errorutils.HTTPResponse(err); resp != nil {
log.Printf("FCM service responded with HTTP %d", resp.StatusCode)
for key, value := range resp.Header {
log.Printf(">>> %s: %v", key, value)
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
log.Print(">>>")
log.Printf(">>> %s", string(b))
return
}
.Net
try
{
await FirebaseMessaging.DefaultInstance.SendAsync(createMyMessage());
}
catch (FirebaseMessagingException ex)
{
var response = ex.HttpResponse
if response != null
{
Console.WriteLine($"FCM service responded with HTTP { response.StatusCode}");
var headers = response.Headers;
for (var entry in response.Headers)
{
Console.WriteLine($">>> {entry.Key}: {entry.Value}");
}
var body = await response.Content.ReadAsString();
Console.WriteLine(">>>");
Console.WriteLine($">>> {body}");
}
}