Yönetici SDK'sı hata işleme

Yönetici SDK'sı hataları iki kategoriye ayrılır:

  1. Programlama hataları: Kullanıcı uygulamasındaki programlama ve konfigürasyon hatalarıdır. Çoğunlukla SDK'nın yanlış kullanımı ( null değerleri kabul etmeyen bir yönteme null iletmek gibi) ve Firebase projesi veya SDK düzeyindeki diğer yapılandırma hataları (eksik kimlik bilgileri, hatalı proje kimliği dizesi vb.) nedeniyle oluşurlar. Açık).
  2. API hataları: Bunlar, SDK uygulamasında meydana gelen çeşitli kurtarılabilir hataları, Firebase arka uç hizmetlerinden kaynaklanan tüm hataları ve RPC çağrıları yaparken oluşabilecek diğer geçici hataları (zaman aşımları gibi) içerir.

Admin SDK, söz konusu platforma özgü bir hata atarak programlama hatalarını bildirir.

  • Java: IllegalArgumentException , NullPointerException veya benzeri yerleşik çalışma zamanı hata türünün örneklerini atar.
  • Python: ValueError , TypeError veya diğer yerleşik hata türünün örneklerini yükseltir.
  • Git: Genel bir hata döndürür.
  • .NET: ArgumentException , ArgumentNullException veya benzer yerleşik hata türünün örneklerini atar.

Çoğu durumda programlama hatalarını açıkça ele almamalısınız. Bunun yerine, programlama hatalarını tamamen önlemek için kodunuzu ve yapılandırmanızı düzeltmelisiniz. Aşağıdaki Java pasajını göz önünde bulundurun:

String uid = getUserInput();

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

getUserInput() yöntemi null veya boş dizeler döndürürse FirebaseAuth.getUser() API, bir IllegalArgumentException oluşturur. Bunu açıkça ele almak yerine getUserInput() yönteminin hiçbir zaman geçersiz bir UID dizesi döndürmemesini sağlayarak sorunu hafifletebilirsiniz. Bu mümkün değilse, gerekli argüman kontrolünü kendi kodunuzda aşağıdaki gibi uygulayın:

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

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

Prensip olarak programlama hatalarını asla tekrar denemeyin. Programlama hatalarında hızlı anlambilime izin vermek çoğu zaman en iyi eylem yöntemidir çünkü geliştirme sırasında programlama hatalarını ve yapılandırma hatalarını ortaya çıkarır ve bunların derhal düzeltilebilmesini sağlar. Bu bağlamda hızlı hata, hataların uygulamanızdaki genel hata işleyicisine yayılmasına izin vermek veya bunları yalnızca denetim amacıyla günlüğe kaydetmek ve ardından mevcut yürütme akışının sonlandırılması anlamına gelebilir (uygulamanın çökmesine gerek olmamalıdır). Genel olarak, programlama diliniz ve uygulama çerçeveniz için en iyi hata giderme uygulamalarını takip edin. Bu tek başına genellikle bu tür hatalarla doğru şekilde başa çıkmak için yeterlidir.

Genellikle hata işleme çabalarınızın büyük kısmı API hatalarını ele almaya odaklanacaktır. Geçici olarak kullanılamayan bir hizmetten kaynaklanan hatalar gibi bu hataların bazıları kurtarılabilir niteliktedir ve bazıları, geçersiz veya süresi dolmuş kimlik belirteçlerinin tespit edilmesi gibi normal program yürütme akışı sırasında bile tahmin edilebilir. Bu kılavuzun geri kalanında Admin SDK'nın bu tür API hatalarını nasıl temsil ettiği ve bunları işlemek için kullanılabilen çeşitli seçenekler özetlenmektedir.

API hatasının yapısı

Bir API hatası aşağıdaki bileşenlerden oluşur:

  1. Hata kodu
  2. Hata mesajı
  3. Servis hata kodu (İsteğe bağlı)
  4. HTTP yanıtı (İsteğe bağlı)

Her API hatasının bir hata kodu ve bir hata mesajı içermesi garanti edilir. Bazı API hataları, hatayı oluşturan API'ye özel bir hizmet hata kodu da içerir. Örneğin, Firebase Auth API tarafından oluşturulan bazı hatalar, Firebase Auth'a özel bir hizmet hata kodu içerir. Hata, bir arka uç hizmetinden gelen bir HTTP hata yanıtının sonucuysa, API hatası aynı zamanda karşılık gelen HTTP yanıtını da içerir. Bu, orijinal yanıtın tam başlıklarını ve içeriğini incelemek için kullanılabilir; bu, hata ayıklama, günlüğe kaydetme veya daha karmaşık hata işleme mantığının uygulanması için faydalıdır.

Node.js dışındaki tüm Admin SDK uygulamaları, yukarıdaki API hata bileşenlerine erişime olanak tanıyan API'ler sağlar.

Dile göre hata türleri ve API'ler

Java

Java'da tüm API hataları FirebaseException sınıfını genişletir. Bu temel sınıftan hata koduna, hata mesajına ve isteğe bağlı HTTP yanıtına erişebilirsiniz.

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

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

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

Hizmet hata kodlarını açığa çıkaran API'ler, FirebaseException öğesinin API'ye özgü alt sınıflarını sağlar. Örneğin, FirebaseAuth API'sindeki tüm genel yöntemlerin FirebaseAuthException örneklerini oluşturacağı bildirildi. Servis hata koduna bu türetilmiş sınıftan ulaşabilirsiniz.

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

Python

Python'da tüm API hataları, exceptions.FirebaseError sınıfını genişletir. Bu temel sınıftan hata koduna, hata mesajına ve isteğe bağlı HTTP yanıtına erişebilirsiniz.

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

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

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

Ayrıca Python Admin SDK, her hata kodu için ayrı türetilmiş sınıflar sunar. Bunları platform hata sınıfları olarak adlandırıyoruz.

class InvalidArgumentError(FirebaseError):
    # ...

class NotFoundError(FirebaseError):
    # ...

Kodunuzda FirebaseError yakalayıp code kontrol edebilir veya bir platform hata sınıfına karşı isinstance() kontrolü gerçekleştirebilirsiniz. Veya belirli platform hata türlerini doğrudan yakalamak için kod yazabilirsiniz. İkinci yaklaşımın daha okunabilir hata işleme koduyla sonuçlanması muhtemeldir.

Hizmet hata kodlarını açığa çıkaran API'ler, platform hata sınıflarının API'ye özgü alt sınıflarını sağlar. Örneğin, auth modülündeki tüm genel yöntemler, auth.UserNotFoundError ve auth.ExpiredIdTokenError gibi API'ye özgü hata türlerini atabilir.

class UserNotFoundError(exceptions.NotFoundError):
    # …

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

Gitmek

Go Admin SDK, hata kodlarının test edilmesine olanak tanıyan bir dizi işlevi içeren bir errorutils paketi sağlar.

package errorutils

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

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

Hata mesajı, bir hatanın Error() işlevi tarafından döndürülen dizedir. İsteğe bağlı HTTP yanıtına, *http.Response değerini döndüren errorutils.HTTPResponse() işlevi çağrılarak erişilebilir.

errorutils paketindeki hata kontrol işlevlerine nil veya başka bir hata değeri iletmek güvenlidir. Giriş argümanı gerçekten söz konusu hata kodunu içeriyorsa true döndürürler ve diğer her şey için false değerini döndürürler. HTTPResponse() işlevi, false yerine nil değerini döndürmesi dışında benzer davranışa sahiptir.

Hizmet hata kodlarını açığa çıkaran API'ler, ilgili paketlerde API'ye özgü hata denetimi işlevleri sağlar. Örneğin, auth paketi IsUserNotFound() ve IsExpiredIDTokenError() işlevlerini sağlar.

.AÇIK

.NET'te tüm API hataları FirebaseException sınıfını genişletir. Bu temel sınıftan platform hata koduna, hata mesajına ve isteğe bağlı HTTP yanıtına erişebilirsiniz.

public class FirebaseException : Exception {

    public ErrorCode ErrorCode { get; }

    public String Message { get; }

    public HttpResponseMessage HttpResponse { get; }
}

Hizmet hata kodlarını açığa çıkaran API'ler, FirebaseException öğesinin API'ye özgü alt sınıflarını sağlar. Örneğin, FirebaseAuth API'sindeki tüm genel yöntemlerin FirebaseAuthException örneklerini oluşturacağı bildirildi. Servis hata koduna bu türetilmiş sınıftan ulaşabilirsiniz.

public class FirebaseAuthException : FirebaseException {

    public AuthErrorCode AuthErrorCode { get; }
}

Platform hata kodları

Hata kodları tüm Firebase ve Google Cloud Platform hizmetlerinde ortaktır. Aşağıdaki tablo tüm olası platform hata kodlarını özetlemektedir. Bu istikrarlı bir listedir ve uzun süre değişmeden kalması beklenmektedir.

GEÇERSİZ ARGÜMAN Müşteri geçersiz bir bağımsız değişken belirtti.
FAILED_PRECONDITION Boş olmayan bir dizinin silinmesi gibi mevcut sistem durumunda istek yürütülemez.
OUT_OF_RANGE İstemci geçersiz bir aralık belirtti.
DOĞRULANMAMIŞ Eksik, geçersiz veya süresi dolmuş OAuth jetonu nedeniyle isteğin kimliği doğrulanamadı.
İZİN REDDEDİLDİ İstemcinin yeterli izni yok. Bunun nedeni OAuth belirtecinin doğru kapsamlara sahip olmaması, istemcinin izne sahip olmaması veya API'nin istemci projesi için etkinleştirilmemiş olmasıdır.
BULUNAMADI Belirtilen kaynak bulunamadı veya beyaz listeye alınma gibi açıklanmayan nedenlerden dolayı istek reddedildi.
ANLAŞMAZLIK Okuma-değiştirme-yazma çakışması gibi eşzamanlılık çakışması. Yalnızca birkaç eski hizmet tarafından kullanılır. Çoğu hizmet bunun yerine ABORTED veya ALREADY_EXISTS kullanır. Kodunuzda hangisinin işleneceğini görmek için hizmete özel belgelere bakın.
DURDURULDU Okuma-değiştirme-yazma çakışması gibi eşzamanlılık çakışması.
ZATEN VAR Bir istemcinin oluşturmaya çalıştığı kaynak zaten mevcut.
RESOURCE_EXHAUSTED Kaynak kotası bitti veya oran sınırlamasına ulaşıldı.
İPTAL EDİLDİ İstek müşteri tarafından iptal edildi.
VERİ KAYBI Kurtarılamaz veri kaybı veya veri bozulması. İstemci hatayı kullanıcıya bildirmelidir.
BİLİNMEYEN Bilinmeyen sunucu hatası. Genellikle bir sunucu hatası.

Bu hata kodu aynı zamanda yerel yanıt ayrıştırma (normal olmayan) hatalarına ve kolayca teşhis edilemeyen çok çeşitli diğer düşük seviyeli G/Ç hatalarına da atanır.

DAHİLİ İç Sunucu Hatası. Genellikle bir sunucu hatası.
KULLANIM DIŞI Hizmet kullanılamıyor. Genellikle sunucu geçici olarak kapalıdır.

Bu hata kodu aynı zamanda yerel ağ hatalarına da atanır (bağlantı reddedildi, ana bilgisayara rota yok).

DEADLINE_EXCEEDED Talep süresi aşıldı. Bu, yalnızca arayan kişinin hedef API'nin varsayılan son tarihinden daha kısa bir son tarih belirlemesi durumunda (yani talep edilen son tarih, sunucunun isteği işlemesi için yeterli değilse) ve isteğin son tarih içinde tamamlanmaması durumunda gerçekleşir.

Bu hata kodu aynı zamanda yerel bağlantıya ve okuma zaman aşımlarına da atanır.

Çoğu API, yukarıdaki hata kodlarının yalnızca bir alt kümesiyle sonuçlanabilir. Her durumda, hata işleyicilerinizi uygularken tüm bu hata kodlarını açıkça işlemeniz beklenmez. Çoğu uygulama yalnızca 1-2 spesifik hata koduyla ilgilenir ve geri kalan her şeyi genel, kurtarılamaz bir arıza olarak ele alır.

Hizmete özel hata kodları

Firebase Yetkilendirmesi

CERTIFICATE_FETCH_FAILED Bir JWT'yi (kimlik belirteci veya oturum çerezi) doğrulamak için gereken ortak anahtar sertifikaları getirilemedi.
BU E-POSTA ZATEN VAR Sağlanan e-postaya sahip bir kullanıcı zaten mevcut.
EXPIRED_ID_TOKEN verifyIdToken() için belirtilen kimlik belirtecinin süresi doldu.
EXPIRED_SESSION_COOKIE verifySessionCookie() iis için belirtilen oturum çerezinin süresi doldu.
INVALID_DYNAMIC_LINK_DOMAIN Sağlanan dinamik bağlantı etki alanı, mevcut proje için yapılandırılmamış veya yetkilendirilmemiş. E-posta eylem bağlantısı API'leriyle ilgilidir.
INVALID_ID_TOKEN verifyIdToken() için belirtilen kimlik belirteci geçersiz.
INVALID_SESSION_COOKIE verifySessionCookie() için belirtilen oturum çerezi geçersiz.
PHONE_NUMBER_ALREADY_EXISTS Sağlanan telefon numarasına sahip bir kullanıcı zaten mevcut.
REVOKED_ID_TOKEN verifyIdToken() için belirtilen kimlik belirteci iptal edildi.
REVOKED_SESSION_COOKIE verifySessionCookie() için belirtilen oturum çerezinin süresi doldu.
UNAUTHORIZED_CONTINUE_URL Devam URL'sinin alanı beyaz listede değil. E-posta eylem bağlantısı API'leriyle ilgilidir.
KULLANICI BULUNAMADI Verilen tanımlayıcı için kullanıcı kaydı bulunamadı.

Firebase Bulut Mesajlaşma

THIRD_PARTY_AUTH_ERROR APN sertifikası veya web push kimlik doğrulama API anahtarı geçersiz veya eksikti.
GEÇERSİZ ARGÜMAN İstekte belirtilen bir veya daha fazla bağımsız değişken geçersizdi.
DAHİLİ İç Sunucu Hatası.
KOTA AŞILDI İleti hedefi için gönderme sınırı aşıldı.
SENDER_ID_MISMATCH Kimliği doğrulanmış gönderen kimliği, kayıt jetonunun gönderen kimliğinden farklıdır. Bu genellikle gönderenin ve hedef kayıt jetonunun aynı Firebase projesinde olmadığı anlamına gelir.
KULLANIM DIŞI Bulut Mesajlaşma hizmeti geçici olarak kullanılamıyor.
KAYITSIZ Uygulama örneğinin FCM'deki kaydı kaldırıldı. Bu genellikle kullanılan cihaz kayıt jetonunun artık geçerli olmadığı ve yenisinin kullanılması gerektiği anlamına gelir.

Otomatik yeniden denemeler

Admin SDK, belirli hataları kullanıcılara göstermeden önce otomatik olarak yeniden dener. Genel olarak aşağıdaki hata türleri şeffaf bir şekilde yeniden denenir:

  • HTTP 503 (Hizmet Kullanılamıyor) yanıtlarından kaynaklanan tüm API hataları.
  • HTTP 500 (Dahili Sunucu Hatası) yanıtlarından kaynaklanan bazı API hataları.
  • Çoğu düşük seviyeli G/Ç hatası (bağlantı reddedildi, bağlantı sıfırlandı vb.).

SDK, yukarıdaki hataların her birini üstel geri çekilmeyle en fazla 5 defa (orijinal deneme + 4 yeniden deneme) yeniden deneyecektir. İsterseniz uygulama düzeyinde kendi yeniden deneme mekanizmalarınızı uygulayabilirsiniz, ancak bu genellikle gerekli değildir.

Yeniden Dene-Sonra desteği

Admin SDK'nın Go ve .NET uygulamaları, HTTP Retry-After başlığını işleme desteğiyle birlikte gelir. Yani, arka uç sunucuları tarafından gönderilen hata yanıtı standart Retry-After başlığını içeriyorsa, SDK, belirtilen bekleme süresi çok uzun olmadığı sürece yeniden deneme sırasında buna saygı gösterecektir. Retry-After başlığı çok uzun bir bekleme süresi gösteriyorsa SDK, yeniden denemeleri atlayacak ve uygun API hatasını verecektir.

Python Admin SDK şu anda Retry-After başlığını desteklemiyor ve yalnızca basit üstel geri çekilmeyi destekliyor.

API hata işleme örnekleri

Genel bir hata işleyicisinin uygulanması

Çoğu durumda istediğiniz şey, bir API hatası nedeniyle program akışının beklenmeyen şekilde sonlandırılmasını önlemek için geniş bir hata aralığını yakalayan genel bir hata işleyicisidir. Bu tür hata işleyicileri genellikle hataları yalnızca denetim amacıyla günlüğe kaydeder veya karşılaşılan tüm API hataları için başka bir varsayılan hata işleme rutinini çağırır. Farklı hata kodlarıyla veya hataya neden olabilecek nedenlerle ilgilenmeleri gerekmez.

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}')

Gitmek

token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
  log.Printf("Failed to verify ID token: %v", err)
  return
}

performPrivilegedOperation(token)

.Açık

try
{
  var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
  PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex) 
{
  Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}

Hata kodlarının kontrol edilmesi

Bazı durumlarda, tam hata kodlarını incelemek ve farklı bağlama duyarlı hata işleme rutinlerini başlatmak isteyebilirsiniz. Aşağıdaki örnekte, hizmet hata koduna göre daha spesifik hata mesajlarını günlüğe kaydeden bir hata işleyicimiz var.

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}')

Gitmek

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)

.Açık

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

Hem üst düzey hem de hizmet hata kodlarını kontrol ettiğimiz başka bir örneği burada bulabilirsiniz:

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}')

Gitmek

_, 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
}

.Açık

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 yanıtına erişme

Bazı nadir durumlarda, bir arka uç hizmeti tarafından döndürülen HTTP hata yanıtını incelemek ve üzerinde bazı hata işleme eylemleri gerçekleştirmek isteyebilirsiniz. Admin SDK, bu hata yanıtlarının hem başlıklarını hem de içeriğini gösterir. Yanıt içeriği genellikle bir dize veya ham bayt dizisi olarak döndürülür ve gereken herhangi bir hedef formatta ayrıştırılabilir.

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}')

Gitmek

_, 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
}

.Açık

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