Los errores del SDK de administración se dividen en dos categorías:
- Errores de programación: Son errores de programación y configuración en la aplicación de usuario. Ocurren principalmente debido al uso incorrecto del SDK (como pasar
null
a un método que no acepta valoresnull
) y otros errores de configuración en el proyecto de Firebase o a nivel de SDK (credenciales faltantes, cadena de ID de proyecto incorrecta, etc.). en). - Errores de API: estos incluyen varios errores recuperables que ocurren dentro de la implementación del SDK, todos los errores que se originan en los servicios backend de Firebase y otros errores transitorios (como tiempos de espera) que pueden ocurrir al realizar llamadas RPC.
El SDK de administración señala errores de programación arrojando un error nativo de la plataforma en cuestión.
- Java: lanza instancias de
IllegalArgumentException
,NullPointerException
o un tipo de error de tiempo de ejecución integrado similar. - Python: genera instancias de
ValueError
,TypeError
u otro tipo de error integrado. - Ir: devuelve un error genérico.
- .NET: arroja instancias de
ArgumentException
,ArgumentNullException
o un tipo de error integrado similar.
En la mayoría de las situaciones no deberías manejar explícitamente los errores de programación. En su lugar, debe corregir su código y configuración para evitar errores de programación por completo. Considere el siguiente fragmento de Java:
String uid = getUserInput();
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
Si el método getUserInput()
devuelve cadenas null
o vacías, la API FirebaseAuth.getUser()
genera una IllegalArgumentException
. En lugar de manejarlo explícitamente, puede mitigar el problema asegurándose de que el método getUserInput()
nunca devuelva una cadena UID no válida. Si eso no es posible, implemente la verificación de argumentos necesaria en su propio código de la siguiente manera:
String uid = getUserInput();
if (Strings.isNullOrEmpty(uid)) {
log.warn("UID must not be null or empty");
return;
}
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
Como principio, nunca vuelva a intentarlo si hay errores de programación. Permitir una semántica rápida para los errores de programación es a menudo el mejor curso de acción porque expone errores de programación y errores de configuración durante el desarrollo, donde pueden corregirse rápidamente. En este contexto, la falla rápida puede significar permitir que los errores se propaguen a un controlador de errores global en su aplicación, o simplemente registrarlos con fines de auditoría seguido de la terminación del flujo de ejecución actual (la aplicación no debería tener que fallar). En general, siga las mejores prácticas de manejo de errores de su lenguaje de programación y el marco de la aplicación. Esto por sí solo suele ser suficiente para abordar correctamente esta clase de errores.
Normalmente, la mayor parte de sus esfuerzos de manejo de errores se centrarán en manejar errores de API . Algunos de estos errores son recuperables, como los errores que resultaron de un servicio no disponible temporalmente, y algunos incluso se anticipan durante el flujo normal de ejecución del programa, como la detección de tokens de identificación no válidos o vencidos. El resto de esta guía describe cómo el SDK de administración representa dichos errores de API y las diversas opciones disponibles para manejarlos.
Estructura de un error de API
Un error de API consta de los siguientes componentes:
- Código de error
- Mensaje de error
- Código de error de servicio (opcional)
- Respuesta HTTP (opcional)
Se garantiza que cada error de API contiene un código de error y un mensaje de error. Ciertos errores de API también contienen un código de error de servicio específico de la API que generó el error. Por ejemplo, algunos errores generados por la API de Firebase Auth contienen un código de error de servicio específico de Firebase Auth. Si el error fue el resultado de una respuesta de error HTTP de un servicio backend, el error de API también contiene la respuesta HTTP correspondiente. Esto se puede utilizar para inspeccionar los encabezados y el contenido exactos de la respuesta original, lo que resulta útil para depurar, registrar o implementar una lógica de manejo de errores más sofisticada.
Todas las implementaciones de Admin SDK, excepto Node.js, proporcionan API que permiten acceder a los componentes anteriores de errores de API.
Tipos de error y API por idioma
Java
En Java, todos los errores de API extienden la clase FirebaseException
. Puede acceder al código de error, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.
public class FirebaseException extends Exception {
@NonNull
public ErrorCode getErrorCode() {
// ...
}
@NonNull
public String getMessage() {
// ...
}
@Nullable
public IncomingHttpResponse getHttpResponse() {
// ...
}
}
Las API que exponen códigos de error de servicio proporcionan subclases de FirebaseException
específicas de API. Por ejemplo, todos los métodos públicos de la API FirebaseAuth
se declaran para generar instancias de FirebaseAuthException
. Puede acceder al código de error del servicio desde esta clase derivada.
public class FirebaseAuthException extends FirebaseException {
@Nullable
public AuthErrorCode getAuthErrorCode() {
// ...
}
}
Pitón
En Python, todos los errores de API extienden la clase exceptions.FirebaseError
. Puede acceder al código de error, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.
class FirebaseError(Exception):
@property
def code(self):
# ...
@property
def message(self):
# ...
@property
def http_response(self):
# ...
Además, Python Admin SDK ofrece clases derivadas independientes para cada código de error. Nos referimos a ellos como clases de error de plataforma .
class InvalidArgumentError(FirebaseError):
# ...
class NotFoundError(FirebaseError):
# ...
Puede detectar FirebaseError
en su código y verificar su code
, o realizar una verificación isinstance()
con una clase de error de plataforma. O puede escribir código para detectar directamente tipos de errores de plataforma específicos. Es probable que este último enfoque dé como resultado un código de manejo de errores más legible.
Las API que exponen códigos de error de servicio proporcionan subclases de clases de error de plataforma específicas de API. Por ejemplo, todos los métodos públicos en el módulo auth
pueden generar tipos de error específicos de API, como auth.UserNotFoundError
y auth.ExpiredIdTokenError
.
class UserNotFoundError(exceptions.NotFoundError):
# …
class ExpiredIdTokenError(exceptions.InvalidArgumentError):
# ...
Ir
El SDK de Go Admin proporciona un paquete errorutils
que contiene una serie de funciones que permiten probar códigos de error.
package errorutils
func IsInvalidArgument(err error) bool {
// ...
}
func IsNotFound(err error) bool {
// ...
}
El mensaje de error es simplemente la cadena devuelta por la función Error()
de un error. Se puede acceder a la respuesta HTTP opcional llamando a la función errorutils.HTTPResponse()
, que devuelve un *http.Response
.
Es seguro pasar nil
o cualquier otro valor de error a las funciones de verificación de errores en el paquete errorutils
. Devuelven true
si el argumento de entrada realmente contiene el código de error en cuestión y devuelven false
para todo lo demás. La función HTTPResponse()
tiene un comportamiento similar, excepto que devuelve nil
en lugar de false
.
Las API que exponen códigos de error de servicio proporcionan funciones de verificación de errores específicas de la API en los paquetes correspondientes. Por ejemplo, el paquete auth
proporciona las funciones IsUserNotFound()
e IsExpiredIDTokenError()
.
.NETO
En .NET, todos los errores de API amplían la clase FirebaseException
. Puede acceder al código de error de la plataforma, al mensaje de error y a la respuesta HTTP opcional desde esta clase base.
public class FirebaseException : Exception {
public ErrorCode ErrorCode { get; }
public String Message { get; }
public HttpResponseMessage HttpResponse { get; }
}
Las API que exponen códigos de error de servicio proporcionan subclases de FirebaseException
específicas de API. Por ejemplo, todos los métodos públicos de la API FirebaseAuth
se declaran para generar instancias de FirebaseAuthException
. Puede acceder al código de error del servicio desde esta clase derivada.
public class FirebaseAuthException : FirebaseException {
public AuthErrorCode AuthErrorCode { get; }
}
Códigos de error de la plataforma
Los códigos de error son comunes en todos los servicios de Firebase y Google Cloud Platform. La siguiente tabla describe todos los posibles códigos de error de la plataforma. Se trata de una lista estable y se espera que permanezca sin cambios durante un largo período.
ARGUMENTO NO VÁLIDO | El cliente especificó un argumento no válido. |
FAILED_PRECONDITION | La solicitud no se puede ejecutar en el estado actual del sistema, como eliminar un directorio que no esté vacío. |
FUERA DE RANGO | El cliente especificó un rango no válido. |
NO AUTENTICADO | Solicitud no autenticada debido a que falta un token de OAuth, no es válido o ha caducado. |
PERMISO DENEGADO | El cliente no tiene permiso suficiente. Esto puede suceder porque el token de OAuth no tiene los alcances correctos, el cliente no tiene permiso o la API no se ha habilitado para el proyecto del cliente. |
EXTRAVIADO | No se encontró el recurso especificado o la solicitud se rechazó por motivos no revelados, como la inclusión en la lista blanca. |
CONFLICTO | Conflicto de simultaneidad, como conflicto de lectura-modificación-escritura. Solo lo utilizan algunos servicios heredados. La mayoría de los servicios utilizan ABORTED o ALREADY_EXISTS en lugar de esto. Consulte la documentación específica del servicio para ver cuál manejar en su código. |
ABORTADO | Conflicto de simultaneidad, como conflicto de lectura-modificación-escritura. |
YA EXISTE | El recurso que un cliente intentó crear ya existe. |
RECURSO_AGOTADO | Ya sea fuera de la cuota de recursos o alcanzando el límite de velocidad. |
CANCELADO | Solicitud cancelada por el cliente. |
PÉRDIDA DE DATOS | Pérdida de datos irrecuperable o corrupción de datos. El cliente debe informar el error al usuario. |
DESCONOCIDO | Error de servidor desconocido. Normalmente es un error del servidor. Este código de error también se asigna a errores de análisis (desordenación) de respuesta local y a una amplia gama de otros errores de E/S de bajo nivel que no son fácilmente diagnosticables. |
INTERNO | Error Interno del Servidor. Normalmente es un error del servidor. |
INDISPONIBLE | Servicio No Disponible. Normalmente, el servidor está temporalmente inactivo. Este código de error también se asigna a errores de la red local (conexión rechazada, no hay ruta al host). |
DEADLINE_EXCEEDED | Se excedió el plazo de solicitud. Esto sucederá solo si la persona que llama establece una fecha límite más corta que la fecha límite predeterminada de la API de destino (es decir, la fecha límite solicitada no es suficiente para que el servidor procese la solicitud) y la solicitud no finalizó dentro de la fecha límite. Este código de error también se asigna a la conexión local y a los tiempos de espera de lectura. |
La mayoría de las API solo pueden generar un subconjunto de los códigos de error anteriores. En cualquier caso, no se espera que usted maneje explícitamente todos estos códigos de error al implementar sus controladores de errores. La mayoría de las aplicaciones solo estarían interesadas en 1 o 2 códigos de error específicos y tratarían todo lo demás como una falla genérica e irrecuperable.
Códigos de error específicos del servicio
Autenticación de base de fuego
CERTIFICATE_FETCH_FAILED | No se pudieron recuperar los certificados de clave pública necesarios para verificar un JWT (token de ID o cookie de sesión). |
EL EMAIL YA EXISTE | Ya existe un usuario con el correo electrónico proporcionado. |
EXPIRED_ID_TOKEN | El token de ID especificado para verifyIdToken() ha caducado. |
EXPIRED_SESSION_COOKIE | La cookie de sesión especificada para verifySessionCookie() ha caducado. |
INVALID_DYNAMIC_LINK_DOMAIN | El dominio de enlace dinámico proporcionado no está configurado ni autorizado para el proyecto actual. Relacionado con las API de enlace de acción de correo electrónico. |
INVALID_ID_TOKEN | El token de ID especificado para verifyIdToken() no es válido. |
INVALID_SESSION_COOKIE | La cookie de sesión especificada para verifySessionCookie() no es válida. |
PHONE_NUMBER_ALREADY_EXISTS | Ya existe un usuario con el número de teléfono proporcionado. |
REVOCED_ID_TOKEN | Se revoca el token de ID especificado para verifyIdToken() . |
REVOKED_SESSION_COOKIE | La cookie de sesión especificada para verifySessionCookie() ha caducado. |
UNAUTHORIZED_CONTINUE_URL | El dominio de la URL de continuación no está en la lista blanca. Relacionado con las API de enlace de acción de correo electrónico. |
USUARIO NO ENCONTRADO | No se encontró ningún registro de usuario para el identificador proporcionado. |
Mensajería en la nube de Firebase
THIRD_PARTY_AUTH_ERROR | El certificado APN o la clave API de autenticación web push no eran válidos o faltaban. |
ARGUMENTO NO VÁLIDO | Uno o más argumentos especificados en la solicitud no eran válidos. |
INTERNO | Error Interno del Servidor. |
CUOTA EXCEDIDA | Se superó el límite de envío para el destino del mensaje. |
SENDER_ID_MISMATCH | El ID del remitente autenticado es diferente del ID del remitente del token de registro. Por lo general, esto significa que el remitente y el token de registro de destino no están en el mismo proyecto de Firebase. |
INDISPONIBLE | El servicio de mensajería en la nube no está disponible temporalmente. |
NO REGISTRADO | La instancia de la aplicación no se registró en FCM. Normalmente, esto significa que el token de registro del dispositivo utilizado ya no es válido y se debe utilizar uno nuevo. |
Reintentos automáticos
El SDK de administración reintenta automáticamente ciertos errores antes de exponerlos a los usuarios. En general, los siguientes tipos de errores se reintentan de forma transparente:
- Todos los errores de API resultantes de respuestas HTTP 503 (servicio no disponible).
- Algunos errores de API resultantes de respuestas HTTP 500 (error interno del servidor).
- La mayoría de los errores de E/S de bajo nivel (conexión rechazada, restablecimiento de conexión, etc.).
El SDK reintentará cada uno de los errores anteriores hasta 5 veces (el intento original + 4 reintentos) con un retroceso exponencial. Puede implementar sus propios mecanismos de reintento en el nivel de la aplicación si lo desea, pero normalmente esto no es necesario.
Soporte de reintento después
Las implementaciones Go y .NET del Admin SDK incluyen soporte para manejar el encabezado HTTP Retry-After
. Es decir, si la respuesta de error enviada por los servidores backend contiene el encabezado Retry-After
estándar, el SDK lo respetará al reintentar siempre que la duración de espera especificada no sea muy larga. Si el encabezado Retry-After
indica una duración de espera muy larga, el SDK omitirá los reintentos y arrojará el error de API correspondiente.
Actualmente, el SDK de Python Admin no admite el encabezado Retry-After
y solo admite un retroceso exponencial simple.
Ejemplos de manejo de errores de API
Implementación de un controlador de errores genérico
En la mayoría de los casos, lo que desea es un controlador de errores genérico que detecte una amplia gama de errores para evitar la terminación inesperada del flujo del programa debido a un error de API. Estos manejadores de errores generalmente simplemente registran los errores con fines de auditoría o invocan alguna otra rutina de manejo de errores predeterminada para todos los errores de API encontrados. No necesariamente están interesados en los diferentes códigos de error o los motivos que pueden haber causado el 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());
}
Pitón
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}')
Ir
token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
log.Printf("Failed to verify ID token: %v", err)
return
}
performPrivilegedOperation(token)
.Neto
try
{
var token = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
PerformPrivilegedOperation(token.getUid());
}
catch (FirebaseAuthException ex)
{
Conole.WriteLine($"Failed to verify ID token: {ex.Message}");
}
Comprobando códigos de error
En algunos casos, querrá inspeccionar los códigos de error exactos e invocar diferentes rutinas de manejo de errores basadas en el contexto. En el siguiente ejemplo, tenemos un controlador de errores que registra mensajes de error más específicos según el código de error del servicio.
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());
}
}
Pitón
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}')
Ir
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)
.Neto
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 continuación se muestra otro ejemplo en el que verificamos los códigos de error de nivel superior y de servicio:
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());
}
}
Pitón
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}')
Ir
_, 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
}
.Neto
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}");
}
}
Accediendo a la respuesta HTTP
En algunos casos excepcionales, es posible que desee inspeccionar la respuesta de error HTTP devuelta por un servicio backend y realizar alguna acción de manejo de errores en ella. El SDK de administración expone tanto los encabezados como el contenido de estas respuestas de error. El contenido de la respuesta generalmente se devuelve como una cadena o una secuencia de bytes sin formato y se puede analizar en cualquier formato de destino necesario.
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());
}
}
Pitón
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}')
Ir
_, 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
}
.Neto
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}");
}
}