Guía práctica: Gestionar errores

Introducción

En esta guía se trata la gestión de errores al interactuar con la API de C2C. Gestionar adecuadamente los errores HTTP es una parte esencial de una integración sólida con cualquier servicio de terceros.

Tipos de errores

Los errores en su integración pueden proceder de varias fuentes, que podemos categorizar en cuatro grupos principales:

  • Errores de E/S: Proceden de operaciones de hardware en su dispositivo, como operaciones de lectura/escritura fallidas
  • Errores de la aplicación: Surgen de problemas con el código de su aplicación
  • Errores de red: Ocurren dentro de la pila de red y su biblioteca de redes informa de ellos
  • Errores de la API: Generados por los servicios de backend de Frame.io

Cada categoría de error debe gestionarse de maneras concretas. Esta guía se centra principalmente en los errores de la API, aunque también abordaremos estrategias generales para las otras categorías.

Cómo se devuelven los errores de la API

La API de Frame.io comunica los errores mediante dos mecanismos principales:

  • Códigos de estado: Códigos de error HTTP que indican la naturaleza del problema
  • Mensajes de error: Contenido de carga útil que proporciona detalles adicionales sobre el error, especialmente cuando varias condiciones de error comparten el mismo código de estado

Códigos de estado de error

Los códigos de estado HTTP son respuestas numéricas estandarizadas que comunican el resultado de una solicitud HTTP. Para obtener más información, consulte la documentación de los códigos de estado HTTP de Mozilla o HTTP Cats para disfrutar de un enfoque más visual. Cada punto final de la API de Frame.io especifica un código de estado de éxito esperado, normalmente 200 (OK), 201 (Created) o 204 (No Content). Puede verificar el éxito comprobando estos códigos específicos o confirmando que el código se encuentra dentro del rango 200-299. Los códigos de estado por encima de 399 indican errores. La mayoría de los errores de la API devuelven códigos 4XX (400-499), que hacen referencia a problemas del lado del cliente. Los errores fuera de este rango suelen proceder de la infraestructura de red entre su dispositivo y nuestro servicio, con la notable excepción de 500 (Internal Server Error), que indica un problema inesperado en nuestro servidor. Del mismo modo, una respuesta 404 (Not Found) podrían generarla servicios intermedios en lugar de nuestro backend, a pesar de ser un código 4XX.

Si se encuentra con códigos de estado inesperados, póngase en contacto con nuestro equipo.

Esquemas de carga útil de error

Frame.io devuelve los detalles de los errores en dos formatos: sencillo y detallado. Su lógica de gestión de errores debe acomodar ambos formatos.

Esquema de error sencillo

A continuación, se muestra un ejemplo de una solicitud fallida con un client_secret incorrecto:

$curl -X POST https://api.frame.io/v2/auth/device/code \
> --include \
> --header 'x-client-version: 2.0.0' \
> --form 'client_id=Some-Client-ID' \
> --form 'client_secret=bad_secret' \
> --form 'scope=asset_create offline'

Respuesta:

HTTP/2 400
...
{"error":"invalid_client"}

El esquema sencillo contiene un único campo para identificar el error.

Esquema de error detallado

Para comparar, a continuación se muestra una solicitud sin autorización adecuada:

$curl -X POST https://api.frame.io/v2/devices/heartbeat \
> --header 'Authorization: Bearer bad-token' \
> --header 'x-client-version: 2.0.0' \
> | python -m json.tool

Respuesta:

1{
2 "code": 409,
3 "errors": [
4 {
5 "code": 409,
6 "detail": "The channel you're uploading from is currently paused.",
7 "status": 409,
8 "title": "Channel Paused"
9 }
10 ],
11 "message": "Channel Paused"
12}

Los errores detallados contienen un campo message que identifica el tipo de error.

Determinar el tipo de error

Al gestionar errores de Frame.io, primero compruebe si hay una carga útil de error y, si no la hay, consulte el código de estado HTTP.

A continuación, se muestra una implementación básica de la gestión de errores:

Python
1# Dict of known error codes: native errors.
2ERROR_STATUS_MAP = {
3 429: SlowDownError,
4 ...
5}
6
7# Dict of known error messages: native errros.
8ERROR_MESSAGE_MAP = {
9 "Channel Paused": ChannelPausedError,
10 "invalid_client": InvalidClientError,
11 "slow_down": SlowDownError,
12 ...
13}
14
15def _c2c_extract_error_message(response):
16 """
17 Gets the error message from an error payload. Returns `None`
18 if an error payload is not found.
19 """
20
21 # Try to decode the payload, if it is not JSON return `None`
22 try:
23 payload = response.json()
24 except JSONDecodeError:
25 return None
26
27 # Try the simple error schema first.
28 message = payload.get("error", default=None)
29 if message is not None:
30 return message
31
32 # Now try the detailed schema. Return None if we do not find one.
33 return payload.get("message", default=None)
34
35def _c2c_error_type_from_response(response):
36 """
37 Converts a bad HTTP response into an error.
38 """
39 error_message = _c2c_extract_error_message(response)
40
41 # try to do a lookup of the error type by message.
42 error_type = ERROR_MESSAGE_MAP.get(error_message, default=None)
43 if error_type is not None:
44 return error_type()
45
46 # If not, try to do a lookup by error code.
47 error_type = ERROR_STATUS_MAP.get(response.status_code, default=None)
48 if error_type is not None:
49 return error_type()
50
51 # Otherwise we are going to return an `UnknownAPIError` to signal that we
52 # encoutnered an error from Frame.io's backend servers, but do not know the
53 # message and/or status code.
54 return UnknownAPIError(message=error_message)
55
56def raise_on_frameio_error(response, expected_status):
57 """
58 Raises a native error from an HTTP response if the response indicates an error
59 occured. Expected status should be the status we expect to get (200, 201, 204,
60 etc).
61 """
62
63 # If the status code is less than `400`, then it is not an error status code.
64 if response.status < 400:
65
66 # Check that the status code is the one we expected, otherwise raise an
67 # error.
68 if response.status != expected_status:
69 raise UnexpectedStatusError(
70 expected=expected_status, received=response.status
71 )
72
73 return None
74
75 # Otherwise convert and raise a native error.
76 raise _c2c_error_type_from_response(response)

Las tablas de búsqueda de errores a las que se hace referencia en este ejemplo se proporcionan al final de esta guía.

Errores de AWS

Al cargar fragmentos de archivos, interactúa directamente con AWS S3, que tiene su propio formato de error. Consulte la documentación de errores comunes de AWS para obtener más detalles. Como regla general, los errores no graves de AWS deben volver a intentarse al menos una vez.

Los errores de AWS se devuelven en formato XML:

1<?xml version="1.0" encoding="UTF-8"?>
2<Error>
3 <Code>NoSuchKey</Code>
4 <Message>The resource you requested does not exist</Message>
5 <Resource>/mybucket/myfoto.jpg</Resource>
6 <RequestId>4442587FB7D0A2F9</RequestId>
7</Error>

El elemento Code identifica el tipo de error.

Volver a intentar errores

Cuándo reintentarlo

Las tablas de error de esta guía indican qué errores de la API se deben reintentar. Para errores que no son de la API de operaciones de E/S, bibliotecas de red o AWS, plantéese reintentar aquellos que puedan ser el resultado de condiciones transitorias. La congestión de la red, las interrupciones temporales del servidor o la pérdida de paquetes normalmente justifican reintentos. La mayoría de las bibliotecas de red generan TimeoutError cuando una solicitud tarda demasiado, que es uno de los principales motivos para realizar un reintento.

En caso de duda, reintente una vez

Los entornos informáticos pueden experimentar problemas impredecibles. Incluso con errores que parecen graves, a menudo vale la pena hacer un reintento. Los estados temporales del sistema, las anomalías de hardware (como inversiones de bits causadas por rayos cósmicos) o condiciones raras de la memoria pueden provocar errores aparentemente graves que se resuelven en un segundo intento. Sin embargo, algunos errores no se deben reintentar. Por ejemplo, una respuesta 409: CHANNEL PAUSED al crear un activo indica que el dispositivo está en pausa y no debería realizar ninguna carga. Este estado es deliberado y es poco probable que cambie con un reintento.

Espera exponencial

Frame.io implementa límites de frecuencia, y exceder estos límites produce un error 429: Slow Down o un estado 400 con esta carga útil:

HTTP/2 400
{"error":"slow_down"}

Cuando reciba estas respuestas, implemente una espera exponencial para los reintentos. Una fórmula recomendada para calcular el retraso (en segundos) es:

Python
1delay = min(2 ** attempt / 2, 32.0)

Donde attempt comienza en 0. Esto produce retrasos de 0,5, 1, 2, 4, 8, 16 y 32 segundos, de forma que todos los intentos posteriores esperan 32 segundos.

Fluctuación de la espera

Recomendamos añadir aleatoriedad (fluctuación) a su tiempo de espera para evitar que se sincronicen las solicitudes en varios dispositivos que se recuperan de la misma condición de error. Esto ayuda a mitigar el problema de la estampida, donde muchos dispositivos reintentan simultáneamente tras una interrupción. Un buen enfoque consiste en añadir un desplazamiento aleatorio entre 0 y la mitad del retraso calculado: math.rand(0, delay // 2).

La espera exponencial no solo es esencial para errores de limitación de velocidad, sino que también tiene ventajas a la hora de gestionar fallos de red y E/S en general. Este enfoque permite que las restricciones temporales para los recursos se resuelvan sin carga adicional de sus reintentos.

Detectar el estado de desconexión

Cuando se producen errores de red, pueden indicar que no se puede acceder a Frame.io por los siguientes motivos:

  • La red local no funciona
  • Los servicios de Frame.io están teniendo problemas
  • Un componente de red intermedio está fallando

Es importante detectar estas condiciones. Cuando un error sugiere problemas de conectividad, implemente una tarea de monitorización que compruebe la restauración del servicio e informe al usuario de la desconexión.

Esperar por la conexión y autorización

Diseñe su aplicación para evitar realizar solicitudes innecesarias cuando el dispositivo actualice la autorización, espere por la autorización del usuario o no pueda acceder a Frame.io. Esto reduce la sobrecarga de la red y mejora la experiencia del usuario.

Bloquee todas las llamadas API (excepto a https://api.frame.io/health) cuando detecte un estado de desconexión. Cuando haya problemas de conectividad, inicie una tarea en segundo plano que sondee el punto final de estado y bloquee las llamadas API adicionales hasta que se restaure la conectividad.

De manera similar, si el token caduca, bloquee las llamadas que dependan de la autorización hasta que se emita uno nuevo. Si la actualización del token falla, alerte al usuario para que se vuelva a autenticar.

Al sondear el estado de conexión, aplique el mismo enfoque de espera exponencial descrito anteriormente.

Tiempos de espera de las solicitudes

Configure valores de tiempo de espera apropiados para diferentes tipos de solicitudes:

  • Predeterminado: 15 segundos para las solicitudes básicas
  • Actualización de autorización: 2 minutos para tener en cuenta el posible procesamiento del backend
  • Carga de fragmento de archivo: 5 minutos para adaptarse a redes lentas al transferir datos de mayor tamaño

Ejemplo de controlador de reintentos

A continuación, se muestra una implementación de pseudocódigo que muestra la gestión de errores con espera exponencial:

Python
1# List of errors we know are fatal and should not be retried.
2FATAL_ERRORS = (
3 ChannelPausedError,
4 DevicesDisabledError,
5 ...
6)
7
8# List of errors we know should be retried more than once.
9RETRY_ERRORS = (
10 TimeoutError,
11 NotFoundError,
12 SlowDownError,
13 UnknownAPIError,
14 ...
15)
16
17# List of errors that could be the result of Frame.io being unreachable.
18DISCONNECTED_ERRORS = (
19 TimeoutError,
20 HttpClientError,
21 ...
22)
23
24def retry_with_backoff(next_handler):
25 """
26 Middleware for retrying errors with exponential backoff.
27 """
28
29 def retry_handler(call, retry_count):
30 """
31 Handler for retrying c2c API calls with exponential backoff.
32 """
33
34 error = None
35
36 # We will retry the call 8 times here, totalling 63.5 seconds +- ~32 seconds.
37 for attempt in range(start=1, stop=retry_count + 1):
38
39 # If we are attempting to reach an endpoint that requires authorization
40 # we should wait unil we have valid authorization before attempting
41 # a call. We need to do this each time in case our access_token
42 # expires between attempts.
43 C2C.wait_for_authorized(call)
44
45 # Likewise, we should wait until we are connected to Frame.io to attempt
46 # a call if we are not calling `https://api.frame.io/health`
47 C2C.wait_for_connected(call)
48
49 try:
50 # Return the result on a success.
51 return next_handler(call)
52 except FATAL_ERRORS as error:
53 # If we hit an error we know is fatal, raise the error without
54 # retrying it.
55 raise error
56
57 except RETRY_ERRORS as error:
58 # If we hit an error we know we should retry many times, continue,
59 # but notify our client if we think we may have been disconnected.
60 if type(error) in DISCONNECTED_ERRORS:
61 C2C.notify_disconnected()
62
63 except BaseException as error:
64 # Otherwise, do not retry the call more than once.
65 if attempt > 1:
66 raise error
67
68 # The delay for the next attempt should be no more than 32 seconds.
69 # This algorithm will go: 0.5s, 1s, 2s, 4s, 8s, 16s, 32s, 32s, ...
70 delay = min(2 ** attempt / 2, 32.0)
71
72 # Add some randomness (jitter) to the delay (up to half the value of
73 # the delay in either direction).
74 delay += math.random(-delay, delay) / 2
75
76 # Wait between retries
77 sleep(delay)
78
79 # If we have exhausted all retries,
80 raise error
81
82 return retry_handler

Tablas de errores

En las siguientes tablas se categorizan los errores de la API de Frame.io y se proporciona orientación para gestionarlos. Esto es lo que representa cada columna:

Mensaje: El identificador del mensaje de carga útil del error. Código HTTP: El código de estado HTTP. Tipo de error: Una categoría conceptual del error (se describe en la sección Descripciones). Esquema: El formato de carga útil del error (sencillo o detallado). Reintentar: Recomendación de reintento ( para varios intentos, una vez para un solo reintento, no para errores graves). Los asteriscos (*) indican consideraciones especiales que se describen en la sección Descripciones.

Mensajes de error de Frame.io

MensajeTipo de errorCódigo HTTPEsquemaReintentar
”access_denied”AccessDenied401sencillouna vez
”authorization_pending”AuthorizationPending400sencillo
”Channel Paused”ChannelPaused409sencillono
”expired_token”ExpiredToken400sencillono
”Invalid Argument”InvalidArgument422detalladono
”invalid_client”InvalidClient400sencillono
”Invalid client version”InvalidClientVersion400sencillono
”invalid_grant”InvalidGrant400sencillono
”invalid_request”InvalidRequest400sencillouna vez
”Not Authorized”UnauthorizedClient401detalladono
”slow_down”SlowDown400sencillo
”unauthorized_client”UnauthorizedClient401sencillosí*

Códigos de estado de Frame.io

Código HTTPTipo de errorReintentar
400InvalidRequestuna vez
401UnauthorizedClientno
422InvalidContentTypeno
429SlowDown
500InternalServerError

Errores de AWS

Consulte la documentación de AWS para obtener descripciones detalladas.

ErrorReintentar
InternalError
OperationAborted
RequestTimeout
ServiceUnavailable
SlowDown
[Todos los demás errores]una vez
Analizar errores similares de AWS

Tanto SlowDown como ServiceUnavailable de AWS indican problemas con la velocidad de la solicitud y pueden abordarse de manera similar al error SlowDown de Frame.io, implementando una espera exponencial. De manera similar, InternalError de AWS corresponde conceptualmente a InternalServerError en nuestra API.

Descripciones

AccessDenied

Se devuelve cuando un usuario rechaza la autorización durante el emparejamiento del dispositivo.

AuthorizationPending

Indica que un usuario aún no ha introducido el código de emparejamiento del dispositivo. Continúe con el sondeo tras el periodo de interval especificado en la respuesta del código del dispositivo.

ChannelPaused

El canal del dispositivo estaba en pausa cuando se creó el activo. No vuelva a intentar cargar este activo.

ExpiredToken

El código de emparejamiento del dispositivo ha caducado. Genere un nuevo código y reinicie el proceso de emparejamiento.

InternalServerError

Indica un problema inesperado del backend. Vuelva a intentarlo una vez e informe de los errores 500 a nuestro equipo para que los investiguen. Tenga en cuenta que algunos problemas conocidos devuelven errores 500 cuando deberían devolver InvalidRequest:

  • Intentar cargar en un canal de dispositivo que no exista
  • Solicitar un recuento de fragmentos personalizado no válido

InvalidArgument

Un parámetro de carga útil contenía un valor no válido. Verifique que los valores de los parámetros coincidan con las expectativas de la API.

InvalidContentType

El encabezado Content-Type de la solicitud no es compatible. De manera general, la API acepta:

  • form/multipart (solo puntos finales de autorización)
  • application/x-www-form-urlencoded (todos los puntos finales)
  • application/json (puntos finales que no son de autorización)

InvalidClient

Las credenciales proporcionadas (client_id, client_secret, etc.) no se han reconocido. Verifique las credenciales de su integración.

InvalidClientVersion

El encabezado x-client-version estaba duplicado o contenía una versión semántica no válida.

InvalidGrant

El tipo de concesión de autorización no es válido. Revise las guías de autorización para consultar los valores correctos.

InvalidRequest

Los parámetros de la solicitud o el formato de carga útil son incorrectos. Verifique los nombres de los campos y los formatos de los valores.

Si se recibe durante la actualización del token, su token de actualización ha caducado y debe reiniciar el proceso de autorización.

SlowDown

Ha superado los límites de frecuencia de solicitud. Implemente una espera exponencial para las solicitudes posteriores. Tenga en cuenta que realizar varias solicitudes de código de dispositivo en la misma conexión TCP puede provocar este error: cree nuevas conexiones para cada solicitud de emparejamiento.

UnauthorizedClient

Normalmente indica que falta access_token o ha caducado. Si recibe este error, actualice su token antes de volver a intentarlo.

Si se produce durante la actualización del token, debe reiniciar el proceso de autorización e indicar al usuario que se vuelva a conectar.

Este error también puede producirse al acceder a recursos fuera del ámbito de autorización del dispositivo o cuando un proyecto ha deshabilitado los dispositivos de C2C. Verifique que haya solicitado los ámbitos adecuados durante la autorización.

Si este error se produce durante la actualización del token, deberá reiniciarse todo el proceso de autorización con intervención del usuario.

Próximos pasos

Le recomendamos que se ponga en contacto con nuestro equipo si tiene alguna pregunta y a continuar con la guía avanzada sobre las cargas. Esperamos poder ayudarle con el progreso de su integración. Si aún no lo ha hecho, revise la guía Implementar C2C: Configuración antes de continuar. Necesitará el access_token que se obtiene durante el proceso de autenticación y autorización. Esta guía se basa en la guía básica sobre las cargas y la guía avanzada sobre las cargas.