Admin SDK 錯誤處理

Admin SDK 錯誤分為以下兩類:

  1. 程式設計錯誤:這類錯誤是指 使用者應用程式這些錯誤多半是因為 SDK 的使用方式不正確 (例如將 null 傳遞至不接受 null 值的方法)。 Firebase 專案或 SDK 層級的其他設定錯誤 (缺少 憑證、專案 ID 字串不正確等。
  2. API 錯誤:這些 包含 SDK 實作中發生的各種可復原錯誤。 源自 Firebase 後端服務的錯誤 執行遠端程序呼叫時可能發生的錯誤 (例如逾時)。

Admin SDK 會擲回 程式設計錯誤 有爭議的平台

  • Java:擲回 IllegalArgumentExceptionNullPointerException 的例項 或類似的內建執行階段錯誤類型
  • Python:引發 ValueErrorTypeError 或其他內建錯誤類型的例項。
  • Go:傳回一般錯誤。
  • .NET:擲回 ArgumentExceptionArgumentNullException 或 類似的內建錯誤類型

在大部分的情況下,您都不應明確處理程式設計錯誤。 您應修正程式碼和設定,以免程式錯誤。 請考慮以下 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 錯誤由下列元件組成:

  1. 錯誤代碼
  2. 錯誤訊息
  3. 服務錯誤代碼 (選填)
  4. HTTP 回應 (選填)

每個 API 錯誤保證都會包含錯誤代碼和錯誤訊息。 某些 API 錯誤也含有 API 的專屬服務錯誤代碼 產生錯誤的錯誤訊息例如 Firebase 驗證產生的部分錯誤 API 含有 Firebase 驗證專屬的服務錯誤代碼。如果錯誤 是後端服務 HTTP 錯誤回應的結果,而 API 錯誤 「要求」 包含相對應的 HTTP 回應這類標記可用來檢查 與原始回應完全相同的標頭和內容,這對於 偵錯、記錄或實作更複雜的錯誤處理邏輯。

除了 Node.js 以外,所有 Admin SDK 實作項目都提供的 API 可用於存取 關於 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 類別您可以查看錯誤代碼、錯誤訊息和選用的 HTTP 再從這個基礎類別回應

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.UserNotFoundErrorauth.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() 函式所傳回的字串 錯誤。您可以呼叫 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_PRE 條件 無法在目前的系統狀態中執行要求,例如刪除非空白目錄。
OUT_OF_RANGE 用戶端指定的範圍無效。
未驗證 OAuth 權杖遺失、無效或過期,因此無法驗證要求。
PERMISSION_DENIED 用戶端權限不足。這可能是因為 OAuth 權杖的範圍不正確、用戶端沒有權限,或是用戶端專案尚未啟用 API。
NOT_FOUND 找不到指定資源,或是要求因未揭露的原因 (例如加入許可清單) 而遭到拒絕。
衝突 並行衝突,例如「讀取-修改-寫入」衝突。只有少數舊版服務會使用。大部分的服務都是使用 ABORTED 或 ALREADY_EXISTS,而不是使用 ALREADY_EXISTS。請參閱服務專屬說明文件,瞭解您要在程式碼中處理哪一個項目。
已中止 並行衝突,例如「讀取-修改-寫入」衝突。
ALREADY_EXISTS 用戶端嘗試建立的資源已存在。
RESOURCE_EXHAUSTED 資源配額用盡或達到頻率限制。
已取消 用戶端已取消要求。
DATA_LOSS 發生無法復原的資料遺失或資料毀損情形。用戶端應向使用者回報錯誤。
不明 發生不明的伺服器錯誤。通常是伺服器的錯誤。

這個錯誤代碼也會指派給本機回應剖析 (unmarshal) 錯誤,以及其他各種不易診斷的低階 I/O 錯誤。

僅限內部 發生內部伺服器錯誤。通常是伺服器的錯誤。
目前無法購買 服務無法使用,一般來說,伺服器暫時無法使用。

這個錯誤代碼也屬於本機網路錯誤 (連線遭拒,沒有主機路由)。

DEADLINE_EXCEEDED 已超出要求期限。只有在呼叫端設定的期限短於目標 API 的預設期限 (即要求的期限不足供伺服器處理要求),且要求未在期限內完成時,才會發生這種情況。

這個錯誤代碼也會指派給本機連線和讀取逾時。

大多數 API 都只能產生上述錯誤代碼的子集。無論如何, 無需明確處理所有這些錯誤代碼,表示 實作錯誤處理常式。大多數應用程式只會在 1 至 2 個特定的錯誤代碼,並將其他所有代碼視為無法復原的一般代碼 失敗。

服務專屬錯誤代碼

Firebase Auth

CERTIFICATE_FETCH_FAILED 無法擷取驗證 JWT (ID 權杖或工作階段 Cookie) 所需的公開金鑰憑證。
EMAIL_ALREADY_EXISTS 已有使用者使用你提供的電子郵件地址。
過期 ID_TOKEN 指定給 verifyIdToken() 的 ID 權杖已過期。
EXPIRED_SESSION_COOKIE 指定 verifySessionCookie() 的工作階段 Cookie 已過期。
INVALID_DYNAMIC_LINK_網域 提供的動態連結網域尚未針對目前的專案設定或授權。與電子郵件動作連結 API 相關。
INVALID_ID_TOKEN 指定給 verifyIdToken() 的 ID 權杖無效。
INVALID_SESSION_COOKIE 指定至 verifySessionCookie() 的工作階段 Cookie 無效。
PHONE_NUMBER_ALREADY_EXISTS 你提供的電話號碼已有人使用。
撤銷 ID_TOKEN 指定給 verifyIdToken() 的 ID 權杖已撤銷。
REVOKED_SESSION_COOKIE 指定給 verifySessionCookie() 的工作階段 Cookie 已過期。
UNAUTHORIZED_CONTINUE_URL 接續網址的網域未列入許可清單。與電子郵件動作連結 API 相關。
未找到使用者 找不到指定 ID 的使用者記錄。

Firebase Cloud Messaging

第三方發生錯誤 APN 憑證或網站推送驗證 API 金鑰無效或遺失。
INVALID_ARGUMENT 要求中指定的一或多個引數無效。
僅限內部 內部伺服器錯誤。
超過 8000 訊息目標的傳送限制已超出上限。
SENDER_ID_MISMATCH 已驗證的寄件者 ID 與註冊權杖的寄件者 ID 不同。這通常代表傳送者和目標註冊權杖不在同一個 Firebase 專案中。
目前無法購買 Cloud Messaging」服務暫時無法使用,
尚未註冊 應用程式執行個體已從 FCM 取消註冊。這代表您使用的裝置註冊權杖已失效,必須使用新的權杖。

自動重試

Admin SDK 會在顯示這些錯誤前,自動重試特定錯誤 對使用者而言一般而言,下列類型的錯誤會以公開透明的方式重試:

  • HTTP 503 (Service Unavailable) 回應產生的所有 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}");
  }
}