Comment gérer les erreurs

Présentation

Ce guide couvre la gestion des erreurs lors de l’interaction avec l’API C2C. Une bonne gestion des erreurs HTTP est essentielle pour une intégration robuste avec tout service tiers.

Types d’erreurs

Dans votre intégration, les erreurs peuvent provenir de diverses sources, que nous pouvons classer en quatre groupes principaux :

  • Erreurs d’E/S : proviennent d’opérations matérielles sur votre appareil, comme des opérations de lecture/écriture ayant échoué.
  • Erreurs d’application : résultent de problèmes dans le code de votre application.
  • Erreurs réseau : se produisent dans la pile réseau et sont communiquées par votre bibliothèque réseau.
  • Erreurs d’API : générées par les services backend de Frame.io.

Chaque catégorie d’erreur nécessite une gestion spécifique. Ce guide se concentre principalement sur les erreurs d’API, bien que nous abordions également des stratégies générales pour les autres catégories.

Renvoi des erreurs d’API

L’API Frame.io communique les erreurs par le biais de deux mécanismes principaux :

  • Codes d’état : codes d’erreur HTTP qui indiquent la nature du problème.
  • Messages d’erreur : contenu de payload qui fournit des détails d’erreur supplémentaires, en particulier lorsque plusieurs conditions d’erreur partagent le même code d’état.

Codes d’état d’erreur

Les codes d’état HTTP sont des réponses numériques standardisées qui communiquent le résultat d’une requête HTTP. Pour plus d’informations, consultez la documentation sur les codes d’état HTTP de Mozilla ou HTTP Cats pour une approche plus visuelle. Chaque point d’entrée de l’API Frame.io spécifie un code d’état de réussite attendu, généralement 200 (OK), 201 (Created) ou 204 (No Content). Vous pouvez vérifier la réussite de deux manières : en recherchant ces codes spécifiques ou en confirmant que le code se situe dans la plage 200-299. Les codes d’état supérieurs à 399 indiquent des erreurs. La plupart des erreurs d’API renvoient des codes 4XX (400-499), ce qui indique des problèmes côté client. Les erreurs en dehors de cette plage proviennent généralement de l’infrastructure réseau entre votre appareil et notre service, à l’exception notable de 500 (Erreur de serveur interne), qui indique un problème inattendu au sein de notre serveur. De même, une réponse 404 (Not Found) peut être générée par des services intermédiaires plutôt que par notre backend, bien qu’il s’agisse d’un code 4XX.

Si vous rencontrez des codes d’état inattendus, veuillez en informer notre équipe.

Schémas de payload d’erreur

Frame.io retourne les détails d’erreur dans deux formats : simple et détaillé. Votre logique de gestion des erreurs doit prendre en charge les deux formats.

Schéma d’erreur simple

Voici un exemple de requête échouée avec un client_secret incorrect :

$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'

Réponse :

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

Le schéma simple contient un seul champ pour l’identification de l’erreur.

Schéma d’erreur détaillé

À titre de comparaison, voici une requête sans autorisation appropriée :

$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

Réponse :

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}

Les erreurs détaillées contiennent un champ message qui identifie le type d’erreur.

Détermination du type d’erreur

Lors de la gestion des erreurs Frame.io, vérifiez d’abord la présence d’une payload d’erreur, puis utilisez le code de statut HTTP comme solution de repli si aucune payload n’est présente.

Voici une implémentation de base de la gestion des erreurs :

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)

Les tables de recherche d’erreur référencées dans cet exemple sont fournies à la fin de ce guide.

Erreurs AWS

Lors du chargement de blocs de fichiers, vous interagissez directement avec AWS S3, qui a son propre format d’erreur. Consultez la documentation sur les erreurs courantes d’AWS pour plus de détails. En règle générale, les erreurs AWS non fatales nécessitent au moins une nouvelle tentative.

Les erreurs AWS sont retournées au format 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>

L’élément Code identifie le type d’erreur.

Nouvelle tentative en cas d’erreur

Quand effectuer une nouvelle tentative

Les tableaux d’erreurs contenus dans ce guide indiquent quelles erreurs d’API doivent faire l’objet d’une nouvelle tentative. Pour les erreurs non liées à l’API provenant d’opérations d’E/S, de bibliothèques de mise en réseau ou d’AWS, envisagez d’effectuer une nouvelle tentative pour celles qui peuvent résulter de conditions transitoires. La congestion du réseau, les pannes temporaires de serveur ou la perte de paquets justifient généralement d’effectuer une nouvelle tentative. La plupart des bibliothèques de mise en réseau déclenchent l’erreur TimeoutError lorsqu’une requête prend trop de temps, ce qui constitue une raison idéale d’effectuer une nouvelle tentative.

En cas de doute, effectuez une nouvelle tentative

Les environnements informatiques peuvent connaître des problèmes imprévisibles. Même avec des erreurs qui semblent fatales, il vaut souvent la peine de faire une nouvelle tentative. Les états temporaires du système, les anomalies matérielles (telles que les inversions de bits dues aux rayons cosmiques) ou les conditions de mémoire rares peuvent provoquer des erreurs apparemment fatales qui se résolvent lors d’une seconde tentative. Certaines erreurs, cependant, ne doivent pas faire l’objet d’une nouvelle tentative. Par exemple, une réponse 409: CANAL SUSPENDU lors de la création d’une ressource indique que l’appareil est en pause et ne doit pas charger. Cet état est délibéré et peu susceptible de changer suite à une nouvelle tentative.

Temporisation exponentielle

Frame.io implémente une limitation du taux de requêtes. Le dépassement de ces limites produit soit une erreur 429 : Ralentissement, soit un statut 400 avec cette payload :

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

Lorsque vous recevez ces réponses, mettez en œuvre une temporisation exponentielle pour les nouvelles tentatives. Voici une formule recommandée pour calculer le délai (en secondes) :

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

attempt commence à 0. Cela produit des délais de 0,5 s, 1 s, 2 s, 4 s, 8 s, 16 s, 32 s, toutes les tentatives suivantes attendant 32 secondes.

Variation de temporisation

Nous recommandons d’ajouter un caractère aléatoire (variation) à votre temporisation pour éviter la synchronisation des requêtes entre plusieurs appareils se remettant de la même condition d’erreur. Cela aide à atténuer le phénomène de débandade lorsque de nombreux appareils effectuent simultanément une nouvelle tentative après une panne. Ajouter un décalage aléatoire entre 0 et la moitié du délai calculé constitue une bonne approche : math.rand(0, delay // 2).

Bien que la temporisation exponentielle soit essentielle pour les erreurs limitant le taux de requêtes, elle est également bénéfique pour gérer les pannes de réseau et d’E/S en général. Cette approche permet de résoudre les contraintes temporaires liées aux ressources sans charge supplémentaire provenant de vos nouvelles tentatives.

Détection du statut Déconnecté

Les erreurs réseau peuvent indiquer que Frame.io est inaccessible pour les raisons suivantes :

  • le réseau local est en panne ;
  • les services Frame.io rencontrent des problèmes ;
  • un composant réseau intermédiaire est défaillant.

Il est important de détecter ces conditions. Lorsqu’une erreur suggère des problèmes de connectivité, implémentez une tâche de surveillance qui vérifie la restauration du service et informe l’utilisateur de la déconnexion.

Attente de connexion et d’autorisation

Concevez l’application de manière à éviter les requêtes inutiles lorsque l’appareil actualise l’autorisation, attend l’autorisation de l’utilisateur ou ne peut pas atteindre Frame.io. Ceci réduit la surcharge réseau et améliore l’expérience client.

Bloquez tous les appels d’API (sauf vers https://api.frame.io/health) lorsque vous détectez un état de déconnexion. Lorsque des problèmes de connectivité surviennent, démarrez une tâche en arrière-plan qui enquête sur le point d’entrée d’intégrité et bloque les autres appels API jusqu’à ce que la connectivité soit restaurée.

De même, si le jeton expire, bloquez les appels dépendants de l’autorisation jusqu’à ce qu’un nouveau jeton soit émis. Si l’actualisation du jeton échoue, avertissez l’utilisateur afin qu’il s’authentifie de nouveau.

Lors de l’enquête sur le statut de connexion, appliquez la même approche de temporisation exponentielle décrite précédemment.

Délais d’expiration des requêtes

Configurez des valeurs de délai d’expiration appropriées pour différents types de requêtes :

  • Par défaut : 15 secondes pour les requêtes de base
  • Actualisation d’autorisation : 2 minutes pour tenir compte du traitement potentiel du backend
  • Chargement de bloc de fichier : 5 minutes pour accommoder les réseaux lents lors du transfert de données plus volumineuses

Exemple de gestion de nouvelle tentative

Voici une implémentation en pseudocode montrant la gestion d’erreur avec temporisation exponentielle :

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

Tableaux d’erreurs

Les tableaux suivants catégorisent les erreurs de l’API Frame.io et fournissent des conseils de gestion. Voici ce que représente chaque colonne :

message : identifiant du message de payload d’erreur code http : code d’état HTTP type d’erreur : catégorie d’erreur conceptuelle (détaillée dans la section descriptions) schéma : format de payload d’erreur (simple ou détaillé) nouvelle tentative : recommandation de nouvelle tentative (oui pour plusieurs tentatives, une fois pour une seule nouvelle tentative, non pour les erreurs fatales). Les astérisques (*) indiquent des considérations spéciales détaillées dans la section descriptions.

Messages d’erreur Frame.io

MessageType d’erreurCode HTTPSchémaNouvelle tentative
”access_denied”AccessDenied401simpleune fois
”authorization_pending”AuthorizationPending400simpleoui
”Channel Paused”ChannelPaused409simplenon
”expired_token”ExpiredToken400simplenon
”Invalid Argument”InvalidArgument422detaillénon
”invalid_client”InvalidClient400simplenon
”Invalid client version”InvalidClientVersion400simplenon
”invalid_grant”InvalidGrant400simplenon
”invalid_request”InvalidRequest400simpleune fois
”Not Authorized”UnauthorizedClient401detaillénon
”slow_down”SlowDown400simpleoui
”unauthorized_client”UnauthorizedClient401simpleyes*

Codes d’état de Frame.io

Code HTTPType d’erreurNouvelle tentative
400InvalidRequestune fois
401UnauthorizedClientnon
422InvalidContentTypenon
429SlowDownoui
500InternalServerErroroui

Erreurs AWS

Consultez la documentation d’AWS pour des descriptions détaillées.

ErreurNouvelle tentative
InternalErroroui
OperationAbortedoui
RequestTimeoutoui
ServiceUnavailableoui
SlowDownoui
[Toutes les autres erreurs]une fois
Analyse des erreurs AWS similaires

Les erreurs SlowDown et ServiceUnavailable d’AWS indiquent toutes deux des problèmes de taux de requête et peuvent être traitées de manière semblable à l’erreur SlowDown de Frame.io, en implémentant une temporisation exponentielle. De même, l’erreur InternalError d’AWS correspond conceptuellement à InternalServerError dans notre API.

Descriptions

AccessDenied

Renvoyée lorsqu’un utilisateur refuse l’autorisation pendant le couplage de l’appareil.

AuthorizationPending

Indique qu’un utilisateur n’a pas encore saisi le code de couplage de l’appareil. Poursuivez l’interrogation après le délai interval spécifié dans la réponse du code d’appareil.

ChannelPaused

Le canal de l’appareil a été mis en pause lors de la création de la ressource. Ne tentez pas de charger à nouveau cette ressource.

ExpiredToken

Le code de couplage de l’appareil a expiré. Générez un nouveau code et relancez le processus de couplage.

InternalServerError

Indique un problème de serveur inattendu. Réessayez une fois et signalez les erreurs 500 à notre équipe pour enquête. Notez que certains problèmes connus renvoient des erreurs 500 alors qu’ils devraient renvoyer l’erreur InvalidRequest :

  • Tentative de chargement vers un canal d’appareil inexistant
  • Demande d’un nombre de blocs personnalisé non valide

InvalidArgument

Un paramètre de payload contenait une valeur non valide. Vérifiez que les valeurs des paramètres correspondent aux attentes de l’API.

InvalidContentType

L’en-tête Content-Type de la requête n’est pas pris en charge. L’API accepte généralement :

  • form/multipart (points d’entrée d’autorisation uniquement)
  • application/x-www-form-urlencoded (tous les points d’entrée)
  • application/json (points d’entrée hors autorisation)

InvalidClient

Les informations d’identification fournies (client_id, client_secret, etc.) n’ont pas été reconnues. Vérifiez les informations d’identification de votre intégration.

InvalidClientVersion

L’en-tête x-client-version était dupliqué ou contenait une version sémantique non valide.

InvalidGrant

Le type d’octroi d’autorisation n’est pas valide. Vérifiez les guides d’autorisation pour connaître les valeurs correctes.

InvalidRequest

Les paramètres de la demande ou le format du payload sont incorrects. Vérifiez les noms des champs et les formats des valeurs.

Si vous recevez cette erreur lors de l’actualisation du jeton, cela signifie que votre jeton d’actualisation a expiré et que vous devez redémarrer le processus d’autorisation.

SlowDown

Vous avez dépassé les limites de taux de requête. Implémentez une temporisation exponentielle pour les requêtes suivantes. Notez que créer plusieurs requêtes de code d’appareil sur la même connexion TCP peut déclencher cette erreur. Créez de nouvelles connexions pour chaque demande de couplage.

UnauthorizedClient

Indique généralement un access_token expiré ou manquant. Si vous recevez cette erreur, actualisez le jeton avant de réessayer.

Si cette erreur se produit lors de l’actualisation du jeton, vous devez redémarrer le processus d’autorisation et demander à l’utilisateur de se reconnecter.

Cette erreur peut également se produire lors de l’accès à des ressources situées hors de la portée d’autorisation de l’appareil ou lorsqu’un projet a désactivé les appareils C2C. Vérifiez que vous avez demandé les portées appropriées lors de l’autorisation.

Si cette erreur se produit lors de l’actualisation du jeton, l’ensemble du processus d’autorisation doit être redémarré avec une intervention de l’utilisateur.

Étapes suivantes

Nous vous encourageons à contacter l’équipe pour toute question et à consulter le guide de chargement avancé. Nous nous ferons un plaisir de vous aider à faire avancer votre intégration. Si vous ne l’avez pas encore fait, veuillez consulter le guide Implémentation C2C : configuration avant de continuer. Vous aurez besoin de l’élément access_token obtenu au cours du processus d’authentification et d’autorisation. Ce guide s’appuie sur le guide de chargement de base et le guide de chargement avancé.