Penanganan error Admin SDK

Error Admin SDK dibagi menjadi dua kategori:

  1. Error pemrograman: Ini adalah kesalahan pemrograman dan konfigurasi dalam aplikasi pengguna. Masalah ini sebagian besar terjadi karena penggunaan SDK yang salah (seperti meneruskan null ke metode yang tidak menerima nilai null), dan error konfigurasi lainnya di level project Firebase atau SDK (tidak ada kredensial, string ID project salah, dan sebagainya).
  2. Error API: Error ini menyertakan berbagai error yang dapat dipulihkan yang terjadi dalam implementasi SDK, error yang berasal dari layanan backend Firebase, dan error sementara lainnya (seperti waktu tunggu) yang mungkin terjadi saat melakukan panggilan RPC.

Admin SDK menandai error pemrograman dengan menampilkan error yang berasal dari platform yang bersangkutan.

  • Java: Menampilkan instance IllegalArgumentException, NullPointerException atau jenis error runtime bawaan yang serupa.
  • Python: Memunculkan instance ValueError, TypeError, atau jenis error bawaan lainnya.
  • Go: Menampilkan error umum.
  • .NET: Menampilkan instance ArgumentException, ArgumentNullException, atau jenis {i>error<i} bawaan yang serupa.

Dalam sebagian besar situasi, Anda tidak boleh menangani error pemrograman secara eksplisit. Sebagai gantinya, Anda harus memperbaiki kode dan konfigurasi Anda untuk menghindari {i>error <i}pemrograman sama sekali. Perhatikan cuplikan Java berikut:

String uid = getUserInput();

UserRecord user = FirebaseAuth.getInstance().getUser(uid);

Jika metode getUserInput() menampilkan null atau string kosong, metode FirebaseAuth.getUser() API akan menampilkan IllegalArgumentException. Daripada fokus pada menanganinya secara eksplisit, Anda dapat memitigasi masalah dengan memastikan Metode getUserInput() tidak pernah menampilkan string UID yang tidak valid. Jika tidak mungkin, terapkan pemeriksaan argumen yang diperlukan dalam kode Anda sendiri sebagai berikut:

String uid = getUserInput();
if (Strings.isNullOrEmpty(uid)) {
    log.warn("UID must not be null or empty");
    return;
}

UserRecord user = FirebaseAuth.getInstance().getUser(uid);

Pada prinsipnya, jangan mencoba ulang error pemrograman. Mengizinkan kegagalan cepat semantik pada kesalahan pemrograman sering kali merupakan tindakan terbaik karena mengekspos {i>bug<i} pemrograman dan kesalahan konfigurasi selama pengembangan, di mana mereka dapat segera diperbaiki. Gagal cepat dalam konteks ini berarti membiarkan {i>error<i} diterapkan ke pengendali error global dalam aplikasi Anda, atau hanya mencatatnya untuk tujuan audit yang diikuti dengan penghentian alur eksekusi saat ini (aplikasi tidak seharusnya error). Secara umum, ikuti petunjuk menangani praktik terbaik bahasa pemrograman dan aplikasi Google Workspace for Education. Ini saja sudah cukup untuk menangani kelas penyimpan{i> <i}ini yang sama.

Biasanya, sebagian besar upaya penanganan error akan berfokus pada penanganan API error. Sebagian error ini dapat dipulihkan, seperti error yang disebabkan oleh layanan yang tidak tersedia untuk sementara, dan beberapa bahkan diantisipasi selama alur eksekusi program yang normal, seperti mendeteksi token ID yang tidak valid atau sudah tidak berlaku. Bagian selanjutnya dari panduan ini menguraikan bagaimana Admin SDK merepresentasikan error API tersebut, dan berbagai opsi yang tersedia untuk menanganinya.

Struktur error API

Error API terdiri dari komponen berikut:

  1. Kode error
  2. Pesan error
  3. Kode error layanan (Opsional)
  4. Respons HTTP (Opsional)

Setiap error API dijamin berisi kode error dan pesan error. Error API tertentu juga berisi kode error layanan yang spesifik untuk API tersebut yang menyebabkan error. Misalnya, beberapa error yang dihasilkan oleh Firebase Auth API berisi kode error layanan yang spesifik untuk Firebase Auth. Jika kesalahan adalah hasil respons error HTTP dari layanan backend, error API juga berisi respons HTTP yang sesuai. Alat ini dapat digunakan untuk memeriksa {i>header<i} dan isi respon awal yang tepat, yang berguna untuk proses debug, logging, atau penerapan logika penanganan error yang lebih canggih.

Semua penerapan Admin SDK kecuali Node.js menyediakan API yang memungkinkan akses komponen error API di atas.

Jenis error dan API menurut bahasa

Java

Di Java, semua error API memperluas class FirebaseException. Anda dapat mengakses kode error, pesan error, dan respons HTTP opsional dari class dasar ini.

public class FirebaseException extends Exception {
    @NonNull
    public ErrorCode getErrorCode() {
        // ...
    }

    @NonNull
    public String getMessage() {
        // ...
    }

    @Nullable
    public IncomingHttpResponse getHttpResponse() {
        // ...
    }
}

API yang mengekspos kode error layanan menyediakan subclass khusus API dari FirebaseException. Misalnya, semua metode publik di FirebaseAuth API dideklarasikan untuk menampilkan instance FirebaseAuthException. Anda dapat mengakses kode error layanan dari kelas turunan ini.

public class FirebaseAuthException extends FirebaseException {
    @Nullable
    public AuthErrorCode getAuthErrorCode() {
        // ...
    }
}

Python

Di Python, semua error API memperluas exceptions.FirebaseError . Anda dapat mengakses kode error, pesan error, dan permintaan dari class dasar ini.

class FirebaseError(Exception):
    @property
    def code(self):
          # ...

    @property
    def message(self):
          # ...

    @property
    def http_response(self):
          # ...

Selain itu, Python Admin SDK menawarkan class turunan terpisah untuk setiap kode error. Kami menyebutnya sebagai class error platform.

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Anda dapat menangkap FirebaseError dalam kode Anda dan memeriksa code-nya, atau lakukan pemeriksaan isinstance() terhadap class error platform. Atau Anda bisa menulis kode untuk secara langsung menangkap tipe {i>error<i} platform tertentu. Pendekatan yang terakhir adalah akan menghasilkan kode penanganan error yang lebih mudah dibaca.

API yang mengekspos kode error layanan menyediakan subclass platform khusus API . Misalnya, semua metode publik dalam modul auth dapat menampilkan Jenis error khusus API seperti auth.UserNotFoundError dan auth.ExpiredIdTokenError.

class UserNotFoundError(exceptions.NotFoundError):
    # …

class ExpiredIdTokenError(exceptions.InvalidArgumentError):
    # ...

Go

Go Admin SDK menyediakan paket errorutils yang berisi serangkaian yang memungkinkan pengujian kode {i>error<i}.

package errorutils

func IsInvalidArgument(err error) bool {
    // ...
}

func IsNotFound(err error) bool {
    // ...
}

Pesan error berupa string yang ditampilkan oleh fungsi Error() dari {i>error<i}. Respons HTTP opsional bisa diakses dengan memanggil metode Fungsi errorutils.HTTPResponse(), yang menampilkan *http.Response.

Anda dapat meneruskan nil atau nilai error lainnya ke pemeriksaan error fungsi dalam paket errorutils. Menampilkan true jika argumen input benar-benar berisi kode error yang dimaksud, dan menampilkan false untuk semuanya lain. Fungsi HTTPResponse() memiliki perilaku yang serupa, kecuali jika menampilkan nil, bukan false.

API yang mengekspos kode error layanan menyediakan pemeriksaan error khusus API fungsi-fungsi lainnya dalam paket yang sesuai. Misalnya, paket auth menyediakan fungsi IsUserNotFound() dan IsExpiredIDTokenError().

.NET

Di .NET, semua error API memperluas FirebaseException . Anda dapat mengakses kode error platform, pesan error, dan respons HTTP opsional dari basis ini .

public class FirebaseException : Exception {

    public ErrorCode ErrorCode { get; }

    public String Message { get; }

    public HttpResponseMessage HttpResponse { get; }
}

API yang mengekspos kode error layanan menyediakan subclass khusus API dari FirebaseException. Misalnya, semua metode publik di FirebaseAuth API dideklarasikan untuk menampilkan instance FirebaseAuthException. Anda dapat mengakses kode error layanan dari kelas turunan ini.

public class FirebaseAuthException : FirebaseException {

    public AuthErrorCode AuthErrorCode { get; }
}

Kode error platform

Kode error bersifat umum di semua layanan Firebase dan Google Cloud Platform. Tabel berikut menguraikan semua kode error platform yang mungkin. Ini adalah stabil, dan diperkirakan tidak akan berubah untuk jangka waktu yang lama.

INVALID_ARGUMENT Klien menentukan argumen yang tidak valid.
FAILED_PRECONDITION Permintaan tidak dapat dijalankan dalam status sistem saat ini, seperti menghapus direktori yang tidak kosong.
OUT_OF_RANGE Klien menentukan rentang yang tidak valid.
UNAUTHENTICATED Permintaan tidak diautentikasi karena token OAuth tidak ada, tidak valid, atau sudah tidak berlaku.
PERMISSION_DENIED Klien tidak memiliki izin yang memadai. Hal ini dapat terjadi karena token OAuth tidak memiliki cakupan yang tepat, klien tidak memiliki izin, atau API belum diaktifkan untuk project klien.
NOT_FOUND Resource yang ditentukan tidak ditemukan, atau permintaan ditolak karena alasan yang tidak diungkapkan seperti pemberian izin.
KONFLIK Konflik serentak, seperti konflik baca-ubah-tulis. Hanya digunakan oleh beberapa layanan lama. Sebagian besar layanan menggunakan ABORTED atau ALREADY_EXISTS, bukan ini. Lihat dokumentasi khusus layanan untuk mengetahui layanan mana yang harus ditangani dalam kode Anda.
ABORTED Konflik serentak, seperti konflik baca-ubah-tulis.
ALREADY_EXIST Resource yang klien coba buat sudah ada.
RESOURCE_EXHAUSTED Kuota resource telah habis atau mencapai pembatasan kapasitas.
DIBATALKAN Permintaan dibatalkan oleh klien.
DATA_LOSS Kehilangan data atau kerusakan data yang tidak dapat dipulihkan. Klien harus melaporkan error tersebut kepada pengguna.
TIDAK DIKETAHUI Terjadi error yang tidak diketahui pada server. Biasanya berupa {i>bug<i} server.

Kode error ini juga ditetapkan untuk error penguraian respons lokal (unmarshal), dan berbagai error I/O tingkat rendah lainnya yang tidak mudah didiagnosis.

INTERNAL Error server internal. Biasanya berupa {i>bug<i} server.
TIDAK TERSEDIA Layanan tidak tersedia. Biasanya server sedang mati untuk sementara.

Kode error ini juga ditetapkan untuk error jaringan lokal (koneksi ditolak, tidak ada rute ke host).

DEADLINE_EXCEEDED Batas waktu permintaan terlampaui. Hal ini hanya akan terjadi jika pemanggil menetapkan batas waktu yang lebih pendek dari batas waktu default API target (yaitu batas waktu yang diminta tidak cukup bagi server untuk memproses permintaan), dan permintaan tidak selesai dalam batas waktu tersebut.

Kode error ini juga ditetapkan untuk koneksi lokal dan waktu tunggu baca.

Sebagian besar API hanya dapat menghasilkan subset kode error di atas. Bagaimanapun, Anda tidak diharapkan untuk menangani semua kode kesalahan ini secara eksplisit menerapkan pengendali error Anda. Sebagian besar aplikasi hanya akan tertarik pada 1-2 kode error spesifik dan perlakukan yang lain sebagai generik dan tidak dapat dipulihkan gagal.

Kode error khusus layanan

Firebase Auth

SERTIFIKAT_FETCH_FAILED Gagal mengambil sertifikat kunci publik yang diperlukan untuk memverifikasi JWT (token ID atau cookie sesi).
EMAIL_ALREADY_EXISTS Pengguna dengan email yang diberikan sudah ada.
EXPIRED_ID_TOKEN Masa berlaku token ID yang ditentukan untuk verifyIdToken() sudah berakhir.
BERAKHIR_SESSION_COOKIE Cookie sesi yang ditentukan untuk verifySessionCookie() sudah tidak berlaku.
DOMAIN LINK_DYNAMIC_INVALID Domain link dinamis yang disediakan tidak dikonfigurasi atau diberi otorisasi untuk project saat ini. Terkait dengan API link tindakan email.
TOKEN_ID_TIDAK_VALID Token ID yang ditentukan untuk verifyIdToken() tidak valid.
TIDAK VALID_SESSION_COOKIE Cookie sesi yang ditentukan untuk verifySessionCookie() tidak valid.
PHONE_NUMBER_ALREADY_EXISTS Pengguna dengan nomor telepon yang diberikan sudah ada.
{i>REVOKED_ID_TOKEN<i} Token ID yang ditentukan untuk verifyIdToken() dicabut.
{i>REVOKED_SESSION_COOKIE<i} Cookie sesi yang ditentukan untuk verifySessionCookie() sudah tidak berlaku.
UNAUTHORIZED_CONTINUE_URL Domain continue URL tidak diizinkan. Terkait dengan API link tindakan email.
PENGGUNA_TIDAK_DITEMUKAN Tidak ada data pengguna yang ditemukan untuk ID yang diberikan.

Firebase Cloud Messaging

KETIGA_PARTY_AUTH_ERROR Sertifikat APN atau kunci API autentikasi push web tidak valid atau tidak ada.
INVALID_ARGUMENT Satu atau beberapa argumen yang ditentukan dalam permintaan tidak valid.
INTERNAL Error server internal.
QUOTA_EXCEEDED Batas pengiriman untuk target pesan terlampaui.
SENDER_ID_MISMATCH ID pengirim yang diautentikasi berbeda dengan ID pengirim untuk token pendaftaran. Ini biasanya berarti pengirim dan token pendaftaran target tidak berada dalam project Firebase yang sama.
TIDAK TERSEDIA Layanan Cloud Messaging untuk sementara tidak tersedia.
BELUM TERDAFTAR Instance aplikasi dibatalkan pendaftarannya dari FCM. Hal ini biasanya berarti bahwa token pendaftaran perangkat yang digunakan sudah tidak valid dan token baru harus digunakan.

Percobaan ulang otomatis

Admin SDK akan otomatis mencoba kembali error tertentu sebelum menampilkan error tersebut kepada pengguna. Secara umum, jenis error berikut akan dicoba ulang secara transparan:

  • Semua error API yang dihasilkan dari respons HTTP 503 (Layanan Tidak Tersedia).
  • Beberapa error API yang dihasilkan dari respons HTTP 500 (Error Server Internal).
  • Sebagian besar error I/O tingkat rendah (koneksi ditolak, koneksi direset, dll.).

SDK akan mencoba kembali setiap error di atas hingga 5 kali (upaya awal + 4 percobaan ulang) dengan backoff eksponensial. Anda dapat menerapkan percobaan ulang Anda sendiri mekanisme di tingkat aplikasi jika Anda mau, tetapi biasanya tidak tidak diperlukan.

Dukungan Coba Lagi-Setelah

Implementasi Go dan .NET dari Admin SDK dilengkapi dengan dukungan untuk menangani header Retry-After HTTP. Artinya, jika respons {i>error<i} yang dikirim oleh server backend berisi header Retry-After standar, SDK akan ketika mencoba lagi, selama durasi tunggu yang ditentukan tidak terlalu panjang. Jika header Retry-After menunjukkan durasi tunggu yang sangat lama, SDK akan mengabaikan percobaan ulang dan menampilkan error API yang sesuai.

Python Admin SDK saat ini tidak mendukung header Retry-After, dan hanya mendukung backoff eksponensial sederhana.

Contoh penanganan error API

Mengimplementasikan pengendali error umum

Dalam kebanyakan kasus yang Anda inginkan adalah pengendali {i>error<i} generik yang menangkap rentang kesalahan untuk mencegah penghentian alur program yang tidak terduga karena Error API. Pengendali {i>error<i} itu biasanya hanya mencatat {i>error<i} untuk tujuan audit, atau panggil beberapa rutinitas penanganan error default lainnya untuk semua API yang ditemukan yang sama. Mereka belum tentu tertarik pada kode {i>error<i} yang berbeda atau alasan yang mungkin menyebabkan error.

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

Memeriksa kode error

Dalam beberapa kasus, Anda perlu memeriksa kode {i>error<i} yang tepat, dan memanggil penanganan {i>error<i} pada konteks yang berbeda-beda. Pada contoh berikut, memiliki pengendali {i>error<i} yang mencatat log pesan {i>error<i} yang lebih spesifik berdasarkan kode error layanan.

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

Berikut contoh lain saat kami memeriksa kode error tingkat teratas dan layanan:

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

Mengakses respons HTTP

Dalam beberapa kasus yang jarang terjadi, Anda mungkin ingin memeriksa respons kesalahan HTTP yang ditampilkan oleh layanan backend dan melakukan beberapa tindakan penanganan {i>error<i}. Admin SDK mengekspos header dan konten respons error ini. Respons konten biasanya dikembalikan sebagai string atau urutan byte mentah, dan dapat diuraikan menjadi format target apa pun yang diperlukan.

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