Instruções: Como lidar com erros

Introdução

Este guia aborda o tratamento de erros ao interagir com a API C2C. O tratamento adequado de erros HTTP é um componente essencial de uma integração robusta com qualquer serviço de terceiros.

Tipos de erros

Os erros em sua integração podem ter diversas origens, que podemos classificar em quatro grupos principais:

  • Erros de E/S: originam-se de operações de hardware no seu dispositivo, como falhas em operações de leitura/gravação
  • Erros de aplicativo: surgem de problemas no código do aplicativo
  • Erros de rede: ocorrem na pilha de rede e são comunicados pela biblioteca de rede
  • Erros de API: gerados pelos serviços de back-end do Frame.io

Cada categoria de erro requer considerações específicas de tratamento. Este guia se concentra principalmente em erros de API, embora também abordemos estratégias gerais para as outras categorias.

Como os erros de API são retornados

A API do Frame.io comunica erros por meio de dois mecanismos principais:

  • Códigos de status: códigos de erro HTTP que indicam a natureza do problema
  • Mensagens de erro: conteúdo que fornece detalhes adicionais sobre o erro, especialmente quando várias condições de erro compartilham o mesmo código de status

Códigos de status de erro

Os códigos de status HTTP são respostas numéricas padronizadas que informam o resultado de uma solicitação HTTP. Para mais informações, consulte a documentação sobre códigos de status HTTP da Mozilla ou HTTP Cats, que oferece uma abordagem mais visual. Cada ponto de acesso da API do Frame.io especifica um código de status de sucesso esperado, normalmente 200 (OK), 201 (Created) ou 204 (No Content). Você pode verificar se a solicitação foi bem-sucedida verificando esses códigos específicos ou confirmando se o código está dentro do intervalo de 200 a 299. Códigos de status acima de 399 indicam erros. A maioria dos erros de API retorna códigos 4XX (400-499), o que significa problemas do lado do cliente. Erros fora desse intervalo geralmente se originam da infraestrutura de rede entre o seu dispositivo e o nosso serviço, com a notável exceção do 500 (Internal Server Error), que indica um problema inesperado em nosso servidor. Da mesma forma, uma resposta 404 (Not Found) pode ser gerada por serviços intermediários, e não pelo nosso back-end, apesar de ser um código 4XX.

Caso encontre códigos de status inesperados, notifique nossa equipe.

Esquemas de conteúdo de erros

O Frame.io retorna detalhes de erros em dois formatos: simples e detalhado. Sua lógica de tratamento de erros deve acomodar ambos os formatos.

Esquema de erro simples

Aqui está um exemplo de uma solicitação com falha devido a um client_secret incorreto:

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

Resposta:

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

O esquema simples contém apenas um único campo para identificação de erros.

Esquema detalhado de erros

Para comparação, aqui está uma solicitação sem a devida autorização:

$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

Resposta:

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}

Erros detalhados contêm um campo message que identifica o tipo de erro.

Determinar o tipo de erro

Ao lidar com erros do Frame.io, verifique primeiro se há um conteúdo de erro e, se não houver, recorra ao código de status HTTP.

Aqui está uma implementação básica de tratamento de erros:

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)

As tabelas de consulta de erros mencionadas neste exemplo estão disponíveis no final deste guia.

Erros da AWS

Ao fazer o upload de blocos de arquivos, você interage diretamente com o AWS S3, que possui seu próprio formato de erros. Consulte a documentação sobre erros comuns da AWS para obter mais detalhes. Como regra geral, os erros não fatais da AWS devem ser repetidos pelo menos uma vez.

Os erros da AWS são retornados como 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>

O elemento Code identifica o tipo de erro.

Repetição de tentativas para erros

Quando repetir a tentativa

As tabelas de erros neste guia indicam quais erros de API devem ser repetidos. Para erros não relacionados à API, de operações de E/S, bibliotecas de rede ou da AWS, considere repetir a tentativa para aqueles que possam resultar de condições transitórias. Congestionamento de rede, interrupções temporárias do servidor ou perda de pacotes normalmente justificam tentativas de repetição. A maioria das bibliotecas de rede gera um TimeoutError quando uma solicitação demora demais, o que é um dos principais motivos para uma nova tentativa.

Em caso de dúvida, tente novamente uma vez

Ambientes de computação podem enfrentar problemas imprevisíveis. Mesmo com erros que parecem fatais, muitas vezes vale a pena fazer uma nova tentativa. Estados temporários do sistema, anomalias de hardware (como inversões de bits causadas por raios cósmicos) ou condições raras de memória podem causar erros aparentemente fatais que se resolvem na segunda tentativa. Alguns erros, no entanto, não devem ser repetidos. Por exemplo, uma resposta 409: CHANNEL PAUSED ao criar um ativo indica que o dispositivo está em pausa e não deve fazer o upload. Esse estado é intencional e é improvável que mude com uma nova tentativa.

Backoff exponencial

O Frame.io implementa limitação de taxa, e exceder esses limites gera um erro 429: Slow Down ou um status 400 com este conteúdo:

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

Ao receber essas respostas, implemente o backoff exponencial para novas tentativas. Uma fórmula recomendada para calcular o atraso (em segundos) é:

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

Em que attempt começa em 0. Isso gera atrasos de 0,5 s, 1 s, 2 s, 4 s, 8 s, 16 s e 32 s, com todas as tentativas subsequentes aguardando 32 segundos.

Aleatoriedade do backoff

Recomendamos adicionar aleatoriedade (jitter) ao seu tempo de backoff para evitar a sincronização de solicitações entre vários dispositivos que estão se recuperando da mesma condição de erro. Isso ajuda a mitigar o problema do thundering herd, em que muitos dispositivos tentam novamente simultaneamente após uma interrupção. Uma boa abordagem é adicionar um deslocamento aleatório entre 0 e metade do atraso calculado: math.rand(0, delay // 2).

Embora o backoff exponencial seja essencial para erros de limitação de taxa, ele também é benéfico para lidar com falhas de rede e de E/S em geral. Essa abordagem permite que restrições temporárias de recursos sejam resolvidas sem carga adicional proveniente de suas tentativas de repetição.

Detecção do status de desconexão

Quando ocorrem erros de rede, eles podem indicar que o Frame.io está inacessível devido a:

  • Sua rede local está fora do ar
  • Os serviços do Frame.io estão apresentando problemas
  • Um componente intermediário da rede está com falha

É importante detectar essas condições. Quando um erro sugerir problemas de conectividade, implemente uma tarefa de monitoramento que verifique se o serviço foi restaurado e informe o usuário sobre a desconexão.

Aguardando conexão e autorização

Projete seu aplicativo para evitar fazer solicitações desnecessárias quando o dispositivo estiver atualizando a autorização, aguardando a autorização do usuário ou incapaz de acessar o Frame.io. Isso reduz a sobrecarga da rede e melhora a experiência do usuário.

Bloqueie todas as chamadas de API (exceto para https://api.frame.io/health) ao detectar um estado de desconexão. Quando surgirem problemas de conectividade, inicie uma tarefa em segundo plano que verifique o ponto de acesso de integridade e bloqueie novas chamadas de API até que a conectividade seja restaurada.

Da mesma forma, caso ocorra a expiração do token, bloqueie as chamadas que dependem de autorização até que um novo token seja emitido. Se a atualização do token falhar, avise o usuário para que ele se autentique novamente.

Ao verificar o status da conexão, aplique a mesma abordagem de backoff exponencial descrita anteriormente.

Tempos limite de solicitação

Configure valores de tempo limite adequados para os diferentes tipos de solicitações:

  • Padrão: 15 segundos para solicitações básicas
  • Atualização de autorização: 2 minutos para levar em conta o possível processamento no back-end
  • Upload de blocos de arquivo: 5 minutos para acomodar redes lentas ao transferir dados maiores

Exemplo de manipulador de repetição de tentativa

Aqui está uma implementação em pseudocódigo que mostra o tratamento de erros com backoff 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

Tabelas de erros

As tabelas a seguir classificam os erros da API do Frame.io e fornecem orientações para o tratamento desses erros. Veja a seguir o que cada coluna representa:

message: O identificador do conteúdo da mensagem de erro http code: O código de status HTTP error type: Uma categoria de erro conceitual (detalhada na seção descrições) schema: O formato do conteúdo do erro (simples ou detalhado) retry: Recomendação de nova tentativa (yes para várias tentativas, once para uma única tentativa, no (para erros fatais) Os asteriscos (*) indicam considerações especiais detalhadas na seção descrições.

Mensagens de erro do Frame.io

MensagemTipo do erroCódigo HTTPEsquemaTentar novamente
”access_denied”AccessDenied401simpleonce
”authorization_pending”AuthorizationPending400simpleyes
”Channel Paused”ChannelPaused409simpleno
”expired_token”ExpiredToken400simpleno
”Invalid Argument”InvalidArgument422detailedno
”invalid_client”InvalidClient400simpleno
”Invalid client version”InvalidClientVersion400simpleno
”invalid_grant”InvalidGrant400simpleno
”invalid_request”InvalidRequest400simpleonce
”Not Authorized”UnauthorizedClient401detailedno
”slow_down”SlowDown400simpleyes
”unauthorized_client”UnauthorizedClient401simpleyes*

Códigos de status do Frame.io

Código HTTPTipo do erroTentar novamente
400InvalidRequestonce
401UnauthorizedClientno
422InvalidContentTypeno
429SlowDownyes
500InternalServerErroryes

Erros da AWS

Consulte a documentação da AWS para obter descrições detalhadas.

ErrorTentar novamente
InternalErroryes
OperationAbortedyes
RequestTimeoutyes
ServiceUnavailableyes
SlowDownyes
[All Other Errors]once
Análise de erros semelhantes da AWS

Tanto o SlowDown quanto o ServiceUnavailable da AWS indicam problemas na taxa de solicitações e podem ser tratados de forma semelhante ao erro SlowDown do Frame.io, implementando o backoff exponencial. Da mesma forma, o InternalError da AWS corresponde conceitualmente ao InternalServerError em nossa API.

Descrições

AccessDenied

Retornado quando um usuário recusa a autorização durante o emparelhamento do dispositivo.

AuthorizationPending

Indica que o usuário ainda não inseriu o código de emparelhamento do dispositivo. Continue fazendo a consulta após o período interval especificado na resposta do código do dispositivo.

ChannelPaused

O canal do dispositivo estava pausado quando o ativo foi criado. Não tente fazer o upload deste ativo novamente.

ExpiredToken

O código de emparelhamento do dispositivo expirou. Gere um novo código e reinicie o processo de emparelhamento.

InternalServerError

Indica um problema inesperado no back-end. Tente novamente uma vez e, por favor, relate erros 500 à nossa equipe para investigação. Observe que alguns problemas conhecidos retornam erros 500 quando deveriam retornar InvalidRequest:

  • Tentativa de upload para um canal de dispositivo inexistente
  • Solicitação de uma contagem de blocos personalizada inválida

InvalidArgument

Um parâmetro do conteúdo continha um valor inválido. Verifique se os valores dos parâmetros correspondem às expectativas da API.

InvalidContentType

O cabeçalho Content-Type da solicitação não é suportado. A API geralmente aceita:

  • form/multipart (somente pontos de acesso de autorização)
  • application/x-www-form-urlencoded (todos os pontos de acesso)
  • application/json (pontos de acesso que não sejam de autorização)

InvalidClient

As credenciais fornecidas (client_id, client_secret, etc.) não foram reconhecidas. Verifique as credenciais da integração.

InvalidClientVersion

O cabeçalho x-client-version foi duplicado ou contém uma semantic version inválida.

InvalidGrant

O tipo de concessão de autorização é inválido. Consulte os guias de autorização para obter os valores corretos.

InvalidRequest

Os parâmetros da solicitação ou o formato do conteúdo estão incorretos. Verifique os nomes dos campos e os formatos dos valores.

Se recebido durante a atualização do token, seu token de atualização expirou e você deve reiniciar o processo de autorização.

SlowDown

Você excedeu os limites de taxa de solicitações. Implemente um backoff exponencial para as solicitações subsequentes. Observe que fazer várias solicitações de código de dispositivo na mesma conexão TCP pode acionar esse erro. Crie novas conexões para cada solicitação de emparelhamento.

UnauthorizedClient

Normalmente indica que o access_token está vencido ou ausente. Se você receber esse erro, atualize seu token antes de tentar novamente.

Se ocorrer durante a atualização do token, você deverá reiniciar o processo de autorização e solicitar que o usuário se reconecte.

Esse erro também pode ocorrer ao acessar recursos fora do escopo de autorização do seu dispositivo ou quando um projeto desativou dispositivos C2C. Verifique se você solicitou os escopos apropriados durante a autorização.

Se esse erro ocorrer durante a atualização do token, todo o processo de autorização deverá ser reiniciado com a intervenção do usuário.

Próximas etapas

Recomendamos que você entre em contato com nossa equipe caso tenha alguma dúvida e consulte o guia avançado de uploads. Esperamos poder apoiar o andamento da sua integração. Caso ainda não tenha feito isso, revise o guia Implementação do C2C: Configuração antes de prosseguir. Você precisará do access_token obtido durante o processo de autenticação e autorização. Este guia se baseia no guia de Uploads básicos e no guia de Uploads avançados.