Admin SDK のエラー処理

Admin SDK のエラーは、次の 2 つのカテゴリに分類されます。

  1. プログラミング エラー: プログラミングと構成のエラーです。 表示されます。ほとんどの場合、SDK の誤った使用が原因です (null 値を受け入れないメソッドに null を渡すなど) Firebase プロジェクト レベルまたは SDK レベルでのその他の構成エラー( 間違ったプロジェクト ID 文字列など)。
  2. API エラー: これら SDK の実装内で発生した回復可能なさまざまなエラー、 バックエンド サービスで発生したエラー、 RPC 呼び出し中に発生する可能性のあるエラー(タイムアウトなど)が含まれます。

Admin SDK は、API ネイティブのエラーをスローすることで、プログラミング エラーを通知します。 確認できます。

  • 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 トークンや期限切れの ID トークンの検出など)をサポートします。 このガイドの残りの部分では、このような API エラーが Admin SDK でどのように表されるかについて概説します。 それらに対処するためのさまざまなオプションについて説明します。

API エラーの構造

API エラーは、次のコンポーネントで構成されます。

  1. エラーコード
  2. エラー メッセージ
  3. サービスのエラーコード(省略可)
  4. HTTP レスポンス(省略可)

すべての API エラーには、必ずエラーコードとエラー メッセージが含まれます。 一部の API エラーには、API に固有のサービス エラーコードも含まれています。 確認できます。例: Firebase Auth によって生成されたエラーは、 API には、Firebase Auth に固有のサービス エラーコードが含まれています。エラーが バックエンド サービスからの 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() 関数が返す文字列です。 エラーが発生します。オプションの HTTP レスポンスにアクセスするには、 errorutils.HTTPResponse() 関数。*http.Response を返します。

nil またはその他のエラー値をエラーチェックに渡しても安全です errorutils パッケージの関数を使用します。入力引数が入力引数である場合、true を返します。 問題となっているエラーコードが含まれており、すべてのエラーに対して false を返す 除外しますHTTPResponse() 関数も同様の動作をしますが、 falsenil になりました。

サービスエラーコードを公開する 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(事前の条件付けに失敗) 空でないディレクトリの削除など、現在のシステム状態ではリクエストを実行できません。
範囲外 クライアントが無効な範囲を指定しました。
未認証 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)を取得できませんでした。
メールアドレスがすでに存在する場合 指定されたメールアドレスのユーザーはすでに存在します。
EXPIRED_ID_TOKEN(期限切れ ID トークン) verifyIdToken() に指定された ID トークンは有効期限が切れています。
EXPIRED_SESSION_COOKIE(期限切れのセッション Cookie) verifySessionCookie() に指定されたセッション Cookie の有効期限が切れています。
INVALID_DYNAMIC_LINK_DOMAIN 指定されたダイナミック リンク ドメインは、現在のプロジェクトに対して構成または承認されていません。メール アクション リンクの API に関連します。
INVALID_ID_TOKEN(無効な ID トークン) verifyIdToken() に指定された ID トークンが無効です。
INVALID_SESSION_COOKIE(無効な SESSION_COOKIE) verifySessionCookie() に指定されたセッション Cookie が無効です。
PHONE_NUMBER_ALREADY_EXISTS(電話番号がすでに存在する) 指定した電話番号のユーザーはすでに存在しています。
REVOKED_ID_TOKEN(取り消し済みの ID トークン) verifyIdToken() に指定された ID トークンは取り消されます。
REVOKED_SESSION_COOKIE(セッション Cookie が無効) verifySessionCookie() に指定されたセッション Cookie の有効期限が切れています。
URL: UNAUTHORIZED_CONTINUE_URL 続行 URL のドメインがホワイトリストに登録されていません。メール アクション リンクの API に関連します。
ユーザーの未検出 指定された ID のユーザー レコードが見つかりません。

Firebase Cloud Messaging

third_PARTY_AUTH_ERROR APNs 証明書またはウェブプッシュ認証の API キーが無効であるか、指定されていません。
INVALID_ARGUMENT リクエストで指定された 1 つ以上の引数が無効でした。
INTERNAL 内部サーバーエラーが発生しました。
割り当て超過 メッセージの宛先の送信制限を超えています。
送信者 ID が一致しません 認証済みの送信者 ID は、登録トークンの送信者 ID とは異なります。これは通常、送信者とターゲット登録トークンが同じ Firebase プロジェクトにないことを意味します。
利用不可 Cloud Messaging サービスは一時的に利用できなくなっています。
未登録 アプリ インスタンスが FCM から登録解除されました。これは通常、使用されたデバイス登録トークンが有効でなくなり、新しい登録トークンを使用する必要があることを意味します。

自動再試行

Admin SDK は、特定のエラーを公開する前に自動的に再試行する 提供します。一般に、次の種類のエラーは透過的に再試行されます。

  • HTTP 503(サービス利用不可)レスポンスに起因するすべての API エラー。
  • HTTP 500(内部サーバーエラー)レスポンスに起因する一部の API エラー。
  • ほとんどの低レベルの I/O エラー(接続の拒否、接続のリセットなど)。

SDK は上記の各エラーを最大 5 回再試行します(最初の試行)。 + 4 回の再試行)があります。独自の再試行を実装できる メカニズムを使用することもできますが、これは通常、 必要ありません。

Retry-After のサポート

Admin SDK の Go 実装と .NET 実装では、以下がサポートされます。 HTTP Retry-After ヘッダーの処理。つまり、Pub/Sub から送信されるエラー レスポンスが バックエンド サーバーに標準の 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 レスポンスへのアクセス

まれに、Google から返される HTTP エラー レスポンスを バックエンドサービスに対して エラー処理アクションを実行しますAdmin SDK これらのエラー レスポンスのヘッダーと内容の両方を公開します。レスポンス content は通常、文字列または未加工のバイト シーケンスとして返され、必要に応じて 必要なターゲット形式に変換できます。

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}");
  }
}