Yönetici SDK'sı hataları iki kategoriye ayrılır:
- Programlama hataları: Bunlar,
kullanıcı uygulamasıdır. Çoğunlukla SDK'nın yanlış kullanımından kaynaklanır
(
null
değerlerini kabul etmeyen bir yöntemenull
iletme) ve Firebase projesi veya SDK düzeyindeki diğer yapılandırma hataları (eksik) kimlik bilgileri, yanlış proje kimliği dizesi vb.). - API hataları: Bunlar SDK uygulamasında oluşan çeşitli kurtarılabilir hataları içerir. Firebase arka uç hizmetlerinden kaynaklanan hatalar ve diğer geçici hataları (zaman aşımları gibi) oluşur.
Yönetici SDK'si, programlama hatalarını söz konusu platforma dayanıyor.
- Java:
IllegalArgumentException
,NullPointerException
örneklerini atar yerleşik çalışma zamanı hatası türü. - Python:
ValueError
,TypeError
veya diğer yerleşik hata türlerinin örneklerini ortaya çıkarır. - Go: Genel bir hata döndürür.
- .NET:
ArgumentException
,ArgumentNullException
veya benzer bir yerleşik hata türü.
Çoğu durumda, programlama hatalarını açıkça ele almamanız gerekir. Bunun yerine programlama hatalarını tamamen önlemek için kodunuzu ve yapılandırmanızı düzeltmeniz gerekir. Şu Java snippet'ini ele alalım:
String uid = getUserInput();
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
getUserInput()
yöntemi null
veya boş dizeler döndürürse
FirebaseAuth.getUser()
API'si bir IllegalArgumentException
gönderir. Şunun yerine:
açık bir şekilde ele almazsanız, sorunu azaltmak için
getUserInput()
yöntemi hiçbir zaman geçersiz bir UID dizesi döndürmez. Değilse
mümkün değilse, gerekli bağımsız değişken kontrolünü aşağıdaki şekilde kendi kodunuza 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);
İlke olarak, hiçbir zaman programlama hatalarını yeniden denemeyin. Hataları hızlandırmaya izin verme anlamlandırması genellikle en iyi eylem planıdır çünkü sırasında ortaya çıkardığı programlama hatalarını ve yapılandırma hatalarını ortaya çıkarır. hemen çözülebilir. Bu bağlamda hatanın hızlı olması, hataların uygulamanızdaki genel bir hata işleyiciye aktarılır veya yalnızca günlüğe kaydedilir. gerçekleştirildikten sonra mevcut yürütme akışının sonlandırılması (uygulama kilitlenmek zorunda kalmamalıdır). Genel olarak, en iyi uygulamalarını ele almak için kullanabileceğiniz yararlı bahsedeceğim. Bu tür bir sınıfla doğru şekilde başa çıkmak için çoğu zaman tek başına hatalar.
Genellikle hata giderme çalışmalarınızın büyük kısmı API'yi hataları. Bu hatalardan bazıları kurtarılabilir niteliktedir. Örneğin, geçici olarak kullanılamamaktadır ve hatta bu süre içinde bazılarının normal program yürütme akışı (ör. geçersiz veya süresi dolmuş kimlik jetonlarını algılama) Bu kılavuzun geri kalanında, Admin SDK'nın bu tür API hatalarını nasıl temsil ettiği açıklanmaktadır. kullanıma sunulan çeşitli seçeneklere değineceğiz.
Bir API hatasının yapısı
API hatası aşağıdaki bileşenlerden oluşur:
- Hata kodu
- Hata mesajı
- Hizmet hata kodu (İsteğe bağlı)
- HTTP yanıtı (İsteğe bağlı)
Her API hatasının bir hata kodu ve hata mesajı içereceği garanti edilir. Bazı API hataları, API'ye özgü hizmet hata kodu da içerir. hataya neden olan URL'dir. Firebase Auth tarafından oluşturulan bazı hatalar, API, Firebase Auth'a özgü bir hizmet hata kodu içerir. Hata bir arka uç hizmetinden gelen HTTP hata yanıtının sonucuydu; API hatası karşılık gelen HTTP yanıtını da içerir. Bu metrik, tam başlıkları ve içeriğini görebilirsiniz. Bu bilgiler yalnızca günlük kaydı oluşturma veya daha gelişmiş hata işleme mantığı uygulama.
Node.js dışındaki tüm Yönetici SDK'sı uygulamaları, erişimi etkinleştiren API'ler sağlar yukarıdaki API hatalarının bileşenlerine göz atın.
Dile göre hata türleri ve API'ler
Java
Java'da tüm API hataları FirebaseException
sınıfını genişletir. Buradan:
hata kodu, hata mesajı ve bu temel sınıftan gelen isteğe bağlı HTTP yanıtı.
public class FirebaseException extends Exception {
@NonNull
public ErrorCode getErrorCode() {
// ...
}
@NonNull
public String getMessage() {
// ...
}
@Nullable
public IncomingHttpResponse getHttpResponse() {
// ...
}
}
Hizmet hata kodlarını gösteren API'ler, API'ye özgü alt sınıfları
FirebaseException
Örneğin, FirebaseAuth
API'deki herkese açık tüm yöntemler
FirebaseAuthException
örnekleri attığı açıklandı. Buradan:
hizmet hatası kodu girin.
public class FirebaseAuthException extends FirebaseException {
@Nullable
public AuthErrorCode getAuthErrorCode() {
// ...
}
}
Python
Python'da tüm API hataları exceptions.FirebaseError
uzantısını genişletin
sınıfını kullanır. Hata koduna, hata mesajına ve isteğe bağlı HTTP
yanıtı ekleyin.
class FirebaseError(Exception):
@property
def code(self):
# ...
@property
def message(self):
# ...
@property
def http_response(self):
# ...
Ayrıca, Python Admin SDK'sı her hata kodu için ayrı türetilmiş sınıflar sunar. Bunlara platform hata sınıfları diyoruz.
class InvalidArgumentError(FirebaseError):
# ...
class NotFoundError(FirebaseError):
# ...
Kodunuzda FirebaseError
öğesini yakalayıp code
değerini kontrol edebilirsiniz veya
platform hatası sınıfına göre isinstance()
kontrolü gerçekleştirin. İsterseniz
belirli platform hatası türlerini doğrudan yakalamak için kullanılabilecek kodlardan oluşur. İkinci yaklaşım ise
daha okunabilir hata işleme koduna neden olabilir.
Hizmet hata kodlarını gösteren API'ler, platformun API'ye özel alt sınıflarını sağlar
hata sınıflarından bahsedeceğiz. Örneğin, auth
modülündeki tüm herkese açık yöntemler
auth.UserNotFoundError
ve
auth.ExpiredIdTokenError
.
class UserNotFoundError(exceptions.NotFoundError):
# …
class ExpiredIdTokenError(exceptions.InvalidArgumentError):
# ...
Go
Go Yönetici SDK'sı,errorutils
özellikleri test etmenize olanak tanır.
package errorutils
func IsInvalidArgument(err error) bool {
// ...
}
func IsNotFound(err error) bool {
// ...
}
Hata mesajı, basit bir ifadenin Error()
işlevi tarafından döndürülen
hatası. İsteğe bağlı HTTP yanıtına
errorutils.HTTPResponse()
işlevi, *http.Response
döndürür.
Hata kontrolüne nil
veya başka bir hata değeri güvenle iletilebilir
errorutils
paketindeki işlevlerin kullanımı. Giriş bağımsız değişkeni true
aslında söz konusu hata kodunu içerir ve her şey için false
değerini döndürür
else. HTTPResponse()
işlevi benzer davranışa sahiptir ancak
false
yerine nil
.
Hizmet hata kodlarını gösteren API'ler, API'ye özel hata kontrolü sağlar
işlevlerine karşılık gelir. Örneğin, auth
paketi
IsUserNotFound()
ve IsExpiredIDTokenError()
işlevlerini sağlar.
.NET
.NET'te tüm API hataları FirebaseException
uzantısını genişletir
sınıfını kullanır. Buradan:
platform hata kodu, hata mesajı ve bu tabandaki isteğe bağlı HTTP yanıtı
sınıfını kullanır.
public class FirebaseException : Exception {
public ErrorCode ErrorCode { get; }
public String Message { get; }
public HttpResponseMessage HttpResponse { get; }
}
Hizmet hata kodlarını gösteren API'ler, API'ye özgü alt sınıfları
FirebaseException
Örneğin, FirebaseAuth
API'deki herkese açık tüm yöntemler
FirebaseAuthException
örneklerini bildirdiği açıklandı.
Buradan:
hizmet hatası kodu girin.
public class FirebaseAuthException : FirebaseException {
public AuthErrorCode AuthErrorCode { get; }
}
Platform hata kodları
Hata kodları tüm Firebase ve Google Cloud Platform hizmetlerinde yaygındır. Aşağıdaki tabloda, olası tüm platform hata kodları özetlenmektedir. Bu, uzun süre değişmeden kalması beklenmektedir.
GEÇERSİZ_BAĞ_DEĞİŞKEN | İstemci, geçersiz bağımsız değişken belirtti. |
FAILED_PRECONDITION | İstek, geçerli sistem durumunda yürütülemez (örneğin, boş olmayan bir dizinin silinmesi). |
ARALIK DIŞI | İstemci, geçersiz aralık belirtti. |
KİMLİK DOĞRULAMADI | Eksik, geçersiz veya süresi dolmuş OAuth jetonundan dolayı isteğin kimliği doğrulanamadı. |
PERMISSION_DENIED | İstemci, gerekli izne sahip değil. Bu durum, OAuth jetonunun doğru kapsamlara sahip olmaması, istemcinin izne sahip olmaması veya API'nin istemci projesi için etkinleştirilmemesi nedeniyle ortaya çıkabilir. |
NOT_FOUND | Belirtilen kaynak bulunamadı veya istek, izin verilenler listesine ekleme gibi açıklanmayan nedenlerden dolayı reddedildi. |
ÇAKIŞMA NOKTASI | Eşzamanlılık uyuşmazlığı (ör. okuma-değiştirme-yazma uyuşmazlığı). Yalnızca birkaç eski hizmet tarafından kullanılır. Çoğu hizmet bunun yerine ABORTED veya ALREADY_EXISTS kullanır. Kodunuzda hangisini kullanacağınızı görmek için hizmete özgü belgelere bakın. |
İPTAL EDİLDİ | Eşzamanlılık uyuşmazlığı (ör. okuma-değiştirme-yazma uyuşmazlığı). |
ALREADY_EXISTS | Bir istemcinin oluşturmaya çalıştığı kaynak zaten mevcut. |
RESOURCE_EXHAUSTED | Kaynak kotası kalmadı veya hız sınırlamasına yaklaşılıyor. |
İPTAL EDİLDİ | İstek, istemci tarafından iptal edildi. |
VERİ_KAYBI | Kurtarılamaz veri kaybı veya veri bozulması. İstemci hatayı kullanıcıya bildirmelidir. |
BİLİNMİYOR | Bilinmeyen sunucu hatası. Bu genellikle bir sunucu hatasıdır.
Bu hata kodu, yerel yanıt ayrıştırma (marshal) hatalarına ve kolayca teşhis edilemeyen çok sayıda düşük düzeyli G/Ç hatasına da atanır. |
DAHİLİ | Dahili sunucu hatası. Bu genellikle bir sunucu hatasıdır. |
KULLANILAMIYOR | Hizmet kullanılamıyor. Genellikle sunucu geçici olarak kapalıdır.
Bu hata kodu, yerel ağ hatalarına da atanır (bağlantı reddedildi, ana makineye yol yok). |
DEADLINE_EXCEEDED | İstek bitiş tarihi aşıldı. Bu durum yalnızca çağrıyı yapan kişi, hedef API'nin varsayılan son tarihinden daha kısa bir son tarih belirlerse (ör. istenen son tarih, sunucunun isteği işlemesi için yeterli değil) ve istek son tarih içinde tamamlanmamıştır.
Bu hata kodu, yerel bağlantı ve okuma zaman aşımlarına da atanır. |
Çoğu API, yukarıdaki hata kodlarının yalnızca bir alt kümesine neden olabilir. Her koşulda, tüm bu hata kodlarını açıkça işlemesi beklenmez. yardımcı olur. Çoğu uygulama yalnızca 1-2 belirli hata kodu kullanın ve diğer her şeyi genel, düzeltilemeyen olarak ele alın. başarısız olur.
Hizmete özgü hata kodları
Firebase Auth
CERTIFICATE_FETCH_BAŞARISIZ | JWT'yi (kimlik jetonu veya oturum çerezi) doğrulamak için gereken ortak anahtar sertifikaları getirilemedi. |
EMAIL_ALREADY_EXISTS | Sağlanan e-postaya sahip bir kullanıcı zaten var. |
SÜRESİ_DOLMUŞ_ID_TOKEN | verifyIdToken() için belirtilen kimlik jetonunun süresi doldu.
|
SÜRESİ_DOLMUŞ_SESSION_ÇABA | verifySessionCookie() için belirtilen oturum çerezinin süresi doldu.
|
GEÇERSİZ_DYNAMIC_LINK_ALAN | Sağlanan dinamik bağlantı alan adı, geçerli proje için yapılandırılmadı veya yetkilendirilmedi. E-posta işlemi bağlantısı API'leriyle ilgili. |
GEÇERSİZ_KİMLİĞİ_TOKEN | verifyIdToken() için belirtilen kimlik jetonu geçersiz.
|
GEÇERSİZ_SESSION_COOKIE | verifySessionCookie() için belirtilen oturum çerezi geçersiz.
|
PHONE_NUMBER_ALREADY_EXISTS | Sağlanan telefon numarasına sahip bir kullanıcı zaten var. |
REVOKED_ID_TOKEN | verifyIdToken() için belirtilen kimlik jetonu iptal edilmiş.
|
REVOKED_SESSION_COOKIE: REVOKED_SESSION_COOKIE | verifySessionCookie() için belirtilen oturum çerezinin süresi doldu.
|
YETKİLİ_D_DEVAMI_URL | Devam URL'sinin alanı izin verilenler listesine eklenmemiş. E-posta işlemi bağlantısı API'leriyle ilgili. |
KULLANICI_NOT_FOUND | Belirtilen tanımlayıcı için kullanıcı kaydı bulunamadı. |
Firebase Cloud Messaging
ÜÇÜNCÜ_TARAF_AUTH_HATASI | APNs sertifikası veya web push kimlik doğrulaması API anahtarı geçersiz ya da eksik. |
GEÇERSİZ_BAĞ_DEĞİŞKEN | İstekte belirtilen bir veya daha fazla bağımsız değişken geçersiz. |
DAHİLİ | Dahili sunucu hatası. |
QUOTA_EXCEEDED | İ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önderen ve hedef kayıt jetonunun aynı Firebase projesinde olmadığı anlamına gelir. |
KULLANILAMIYOR | Cloud Messaging hizmeti geçici olarak kullanılamıyor. |
KAYDEDİLMEMİŞ | Uygulama örneğinin FCM'deki kaydı iptal edildi. Bu genellikle, kullanılan cihaz kayıt jetonunun artık geçerli olmadığı ve yeni bir jeton kullanılması gerektiği anlamına gelir. |
Otomatik yeniden denemeler
Yönetici SDK'si, belirli hataları ortaya çıkarmadan önce otomatik olarak yeniden dener değer katmış olacaksınız. 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 alt düzey G/Ç hatası (bağlantı reddedildi, bağlantı sıfırlandı vb.).
SDK, yukarıdaki hataların her birini en fazla 5 kez yeniden dener (orijinal deneme + 4 yeniden deneme) eklendi. Kendi yeniden deneme işleminizi uygulayabilirsiniz mekanizmaları da ekleyebilirsiniz, ancak bu pek çok gereklidir.
Yeniden dene - Destek sonrası
Admin SDK'nın Go ve .NET uygulamaları,
HTTP Retry-After
üstbilgisi işleniyor. Yani, Arkadaş Bitkiler tarafından gönderilen hata yanıtı
arka uç sunucuları standart Retry-After
üstbilgisini içeriyorsa SDK,
belirtilen bekleme süresi çok uzun olmadığı sürece yeniden denemede buna dikkat edin.
gerekir. Retry-After
başlığı çok uzun bir bekleme süresi belirtiyorsa SDK
yeniden denemeleri atlar ve uygun API hatasını verir.
Python Admin SDK'sı şu anda Retry-After
üst bilgisini desteklememektedir ve
yalnızca basit bir üstel geri yüklemeyi destekler.
API hatası işleme örnekleri
Genel bir hata işleyici uygulama
Çoğu durumda, istediğiniz, geniş bir alanı yakalayan genel bir hata işleyici program akışının beklenmedik bir şekilde sonlandırılmasını önlemek için, API hatası. Bu tür hata işleyiciler genellikle hataları denetleme amacıyla günlüğe kaydeder. veya karşılaşılan tüm API için başka bir varsayılan hata işleme rutinini çağırın hatalar. Her zaman farklı hata kodlarıyla veya farklı hata kodlarıyla hataya yol açmış olabilir.
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}");
}
Hata kodlarını kontrol etme
Bazı durumlarda tam olarak hata kodlarını inceleyerek bağlama duyarlı farklı hata işleme rutinleri oluşturabilirsiniz. Aşağıdaki örnekte, cihaza göre daha spesifik hata mesajlarını günlüğe kaydeden bir hata işleyici hizmet hata kodu.
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}");
}
}
Aşağıda, hem üst düzey hem de hizmet hata kodlarını kontrol ettiğimiz başka bir örnek verilmiştir:
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 yanıtına erişme
Nadiren de olsa, bir sağlayıcı tarafından döndürülen HTTP hatası yanıtını bir arka uç hizmetini ve bu hizmet üzerinde bazı hata işleme işlemleri gerçekleştirmenizi öneririz. Yönetici SDK'sı hem üstbilgilerini hem de içeriğini gösterir. Yanıt içerik genellikle bir dize veya ham bayt dizisi olarak döndürülür ve hedef biçimine ayrıştırılır.
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}");
}
}