方法:エラーの処理

はじめに

このガイドでは、C2C API とのやり取りでのエラー処理について説明します。HTTP エラーを適切に処理することは、サードパーティサービスとの堅牢な統合に欠かせない要素です。

エラータイプ

統合において、エラーは様々なソースに起因して発生する可能性があります。これらは、次の 4 つの主要グループに分類できます。

  • **I/O エラー:**読み取り/書き込み操作の失敗など、デバイスのハードウェア操作に起因するもの
  • **アプリケーションエラー:**アプリケーションコード内の問題に起因して発生するもの
  • **ネットワークエラー:**ネットワークスタック内で発生し、お客様のネットワークライブラリから通知されるもの
  • **API エラー:**Frame.io のバックエンドサービスによって生成されるもの

エラーカテゴリーごとに、処理に関して求められる具体的な考慮事項があります。このガイドでは主に API エラーに焦点を当てていますが、他のカテゴリーの一般的な戦略についても説明します。

API エラーが返される仕組み

Frame.io の API では、次の 2 つの主要メカニズムを通じてエラーを通知します。

  • **ステータスコード:**問題の性質を示す HTTP エラーコード
  • **エラーメッセージ:**特に複数のエラー条件が同じステータスコードの場合に、エラーの詳細を追加提供するペイロードコンテンツ

エラーステータスコード

HTTP ステータスコードは、HTTP リクエストの結果を通知する標準化された数値レスポンスです。詳細については、Mozilla の HTTP ステータスコードドキュメントや、より視覚的なアプローチの HTTP Cats を参照してください。

各 Frame.io API エンドポイントは、予期される成功ステータスコード(通常は、200 (OK)201 (Created) または 204 (No Content))を指定します。これらの具体的なコードをチェックするか、コードが 200~299 の範囲内にあることをチェックすることで、成功を確認できます。

399 を超えるステータスコードはエラーです。ほとんどの API エラーは、クライアント側の問題を示す 4XX コード(400~499)を返します。この範囲外のエラーは、通常、デバイスとサービスの間のネットワークインフラストラクチャに起因したものです。特筆すべき例外が 500 (Internal Server Error) で、これはサーバー内の予期しない問題を示します。

同様に、4XX コードであっても、バックエンドではなく中間サービスによって 404 (Not Found) レスポンスが生成される場合があります。

予期しないステータスコードに遭遇した場合は、当社チームにお知らせください。

ペイロードエラーのスキーマ

Frame.io は、エラーの詳細をシンプルと詳細の **2 **つの形式で返します。お客様のエラー処理ロジックは、どちらの形式にも対応している必要があります。

簡易エラースキーマ

不正な client_secret によって失敗したリクエストの例を次に示します。

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

レスポンス:

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

簡易スキーマに含まれているのは、エラー識別用のフィールドが 1 つです。

詳細エラースキーマ

比較のため、適切な権限のないリクエストを次に示します。

$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

レスポンス:

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}

詳細エラーには、エラータイプを識別する message フィールドが含まれています。

エラータイプの特定

Frame.io エラーを処理する際には、まずエラーペイロードを確認し、ペイロードが存在しない場合は HTTP ステータスコードにフォールバックします。

基本的なエラー処理の実装は次のとおりです。

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)

この例で参照されているエラールックアップテーブルは、このガイドの最後に記載されています。

AWS エラー

ファイルチャンクをアップロードする際には、独自のエラー形式を持つ AWS S3 と直接やり取りします。詳細については、AWS の一般的なエラーに関するドキュメントを参照してください。一般的なルールとして、致命的ではない AWS エラーの場合は少なくとも 1 回再試行する必要があります。

AWS エラーは次のような 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>

エラータイプは Code 要素で識別します。

エラーに対する再試行

再試行のタイミング

このガイドのエラー表は、再試行の必要がある API エラーを示しています。I/O 操作、ネットワークライブラリまたは AWS に起因する非 API エラーの場合、一時的な状況に起因する可能性のあるものについては再試行を検討してください。ネットワークの輻輳、一時的なサーバー停止またはパケット損失は、通常、再試行の対象となります。ほとんどのネットワークライブラリは、リクエストに時間がかかりすぎると TimeoutError を返します。これは、再試行の主な候補です。

疑わしい場合は 1 回再試行

コンピューティング環境では、予測不可能な問題が発生する可能性があります。致命的に見えるエラーであっても、多くの場合、1 回は再試行する価値があります。一時的なシステム状態、ハードウェアの異常(宇宙線によるビット反転など)またはまれなメモリ状態によって、致命的に見えるエラーが発生し、次の試行で解決されることがあります。

ただし、再試行すべきではないエラーもあります。例えば、アセット作成時の 409: CHANNEL PAUSED レスポンスは、デバイスが一時停止中でアップロードすべきではないことを示しています。この状態は意図的であり、再試行で変わる可能性はほとんどありません。

指数関数バックオフ

Frame.io にはレート制限が実装されており、これらの制限を超えると 429: Slow Down エラーか、次のペイロードを持つ 400 ステータスが生成されます。

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

これらのレスポンスが返されたら、再試行の指数関数バックオフを適用します。遅延(秒単位)の計算に推奨される式は次のとおりです。

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

ここで、attempt は 0 から始まります。これにより、0.5 秒、1 秒、2 秒、4 秒、8 秒、16 秒、32 秒の遅延が発生します。以降、すべての試行は 32 秒待機します。

Backoff jitter

We recommend adding randomness (jitter) to your backoff timing to prevent request synchronization across multiple devices recovering from the same error condition. This helps mitigate the thundering herd problem where many devices retry simultaneously after an outage. A good approach is to add a random offset between 0 and half the calculated delay: math.rand(0, delay // 2).

指数関数バックオフは、レート制限エラーに不可欠ですが、一般的にはネットワーク障害や I/O 障害の処理にも役立ちます。このアプローチでは、再試行による負荷を増やすことなく、一時的なリソース制約を解決できます。

切断ステータスの検出

ネットワークエラーが発生すると、次の理由で Frame.io に到達できないことが通知されることがあります。

  • ローカルネットワークがダウンしている
  • Frame.io のサービスに問題が発生している
  • 中間ネットワークコンポーネントに障害が発生している

これらの状態を検出することが重要です。エラーが接続の問題を示唆している場合は監視タスクを実装して、サービスの復元をチェックし、切断をユーザーに通知します。

接続と承認の待機

デバイスが承認を更新している、ユーザー承認を待機しているまたは Frame.io に到達できない場合に、不要なリクエストが行われないようにアプリケーションを設計してください。これにより、ネットワークのオーバーヘッドが削減され、ユーザーエクスペリエンスが向上します。

切断状態を検出した場合は、すべての API 呼び出し(https://api.frame.io/health を除く)をブロックします。接続の問題が発生した場合は、正常性エンドポイントをポーリングするバックグラウンドタスクを開始し、接続が復元されるまで以降の API 呼び出しをブロックします。

同様に、トークンの有効期限が切れた場合、新しいトークンが発行されるまで、承認に依存する呼び出しをブロックします。トークンの更新に失敗した場合は、再認証するようユーザーに警告します。

接続ステータスをポーリングする場合は、前述と同じ指数関数バックオフアプローチを適用します。

リクエストタイムアウト

様々なタイプのリクエストに適したタイムアウト値を設定します。

  • デフォルト:基本要求の場合は 15 秒
  • 承認の更新:潜在的なバックエンド処理のために 2 分
  • ファイルチャンクのアップロード:大容量データを転送する場合、低速ネットワークに対応するために 5 分

再試行ハンドラーの例

次の擬似コード実装は、指数関数バックオフによるエラー処理を示しています。

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

エラー表

次の表は、Frame.io API エラーの分類と処理のガイダンスです。各列の内容は次のとおりです。

message:エラーペイロードメッセージ識別子、http code :HTTP ステータスコード、 error type:概念的なエラーカテゴリー(詳細は「説明」セクションを参照)、 schema:エラーペイロード形式(簡易または詳細)、 retry:再試行の推奨事項(yes は複数回の試行、once は 1 回再試行、no は致命的なエラー)

アスタリスク(*)は、特別な考慮事項があり、「説明」セクションで詳しく説明されていることを示しています。

Frame.io のエラーメッセージ

メッセージエラータイプHTTP コードスキーマ再試行
「access_denied」AccessDenied401簡易once
「authorization_pending」AuthorizationPending400簡易yes
「Channel Paused」ChannelPaused409簡易no
「expired_token」ExpiredToken400簡易no
「Invalid Argument」InvalidArgument422詳細no
「invalid_client」InvalidClient400簡易no
「Invalid client version」InvalidClientVersion400簡易no
「invalid_grant」InvalidGrant400簡易no
「invalid_request」InvalidRequest400簡易once
「Not Authorized」UnauthorizedClient401詳細no
「slow_down」SlowDown400簡易yes
「Unauthorized_client」UnauthorizedClient401簡易yes*

Frame.io のステータスコード

HTTP コードエラータイプ再試行
400InvalidRequestonce
401UnauthorizedClientno
422InvalidContentTypeno
429SlowDownyes
500InternalServerErroryes

AWS エラー

詳細については、AWS のドキュメントを参照してください。

エラー再試行
InternalErroryes
OperationAbortedyes
RequestTimeoutyes
ServiceUnavailableyes
SlowDownyes
[その他すべてのエラー]once
Parsing similar AWS errors

Both SlowDown and ServiceUnavailable from AWS indicate request rate issues and can be treated similar to Frame.io’s SlowDown error, implementing exponential backoff. Similarly, AWS’s InternalError corresponds conceptually to InternalServerError in our API.

説明

AccessDenied

デバイスのペアリング中にユーザーが承認を拒否した場合に返されます。

AuthorizationPending

ユーザーがデバイスのペアリングコードをまだ入力していないことを示します。デバイスコードレスポンスに指定されている interval 期間の経過後に、ポーリングを続行します。

ChannelPaused

アセット作成時にデバイスチャネルが一時停止されました。このアセットのアップロードを再試行しないでください。

ExpiredToken

デバイスのペアリングコードの有効期限が切れています。新しいコードを生成し、ペアリングプロセスを再開してください。

InternalServerError

予期しないバックエンドの問題を示します。1 度再試行し、500 エラーを当社チームに報告してください。調査いたします。

既知の問題の中には、InvalidRequest エラーが返されるべきところで 500 エラーが返されるものがあります。

  • 存在しないデバイスチャネルにアップロードしようとしている場合
  • 無効なカスタムチャンク数をリクエストしている場合

InvalidArgument

ペイロードパラメーターに無効な値が含まれていました。パラメーター値が API の予期する値と一致することを確認してください。

InvalidContentType

リクエストの Content-Type ヘッダーはサポートされていません。API は通常、以下を受け入れます。

  • form/multipart (承認エンドポイントのみ)
  • application/x-www-form-urlencoded (すべてのエンドポイント)
  • application/json (非承認エンドポイント)

InvalidClient

指定された資格情報(client_idclient_secret、など)が認識されませんでした。統合資格情報を確認してください。

InvalidClientVersion

x-client-version ヘッダーが重複しているか、ヘッダーに無効なセマンティックバージョンが含まれています。

InvalidGrant

承認付与タイプが無効です。正しい値については、承認ガイドを確認してください。

InvalidRequest

リクエストのパラメーターまたはペイロードの形式が正しくありません。フィールド名と値の形式を確認してください。

トークンの更新中に届いた場合、更新トークンの有効期限が切れており、承認プロセスをやり直す必要があります。

SlowDown

リクエストのレート制限を超えました。後続のリクエストには指数関数バックオフが適用されます。同じ TCP 接続で複数のデバイスコードリクエストを実行すると、このエラーが発生する可能性があることに注意してください。ペアリングリクエストごとに新しい接続を作成してください。

UnauthorizedClient

通常は、access_token が期限切れまたは欠落していることを示します。このエラーが返された場合は、トークンを更新してから再試行してください。

トークンの更新中に発生した場合は、承認プロセスをやり直し、再接続を求めるプロンプトをユーザーに表示する必要があります。

このエラーは、デバイスの承認スコープ外のリソースにアクセスしようとした場合や、プロジェクトで C2C デバイスが無効にされた場合にも発生する可能性があります。承認中に適切なスコープをリクエストしたことを確認してください。

トークンの更新中にこのエラーが発生した場合は、ユーザーが介入して承認プロセス全体をやり直す必要があります。

次のステップ

不明点がありましたら、当社チームまでお問い合わせください。そのうえで、アップロード(高度)ガイドに進んでください。お客様の統合の進捗を支援できる機会を楽しみにしています。

まだの場合は、C2C の実装:セットアップガイドを読んでから先へ進んでください。

認証および承認プロセス中に、access_token の取得が必要になります。

このガイドは、アップロード(基本)ガイドアップロード(高度)ガイドに基づいています。