오류 처리 방법

소개

이 가이드에서는 C2C API와 상호 작용할 때의 오류 처리를 다룹니다. HTTP 오류를 적절하게 처리하는 것은 타사 서비스와 안정적으로 통합하기 위한 필수 구성 요소입니다.

오류 유형

통합 시 발생하는 오류는 다양한 소스에서 발생할 수 있으며, 네 가지 주요 그룹으로 분류할 수 있습니다.

  • I/O 오류: 실패한 읽기/쓰기 작업과 같이 디바이스의 하드웨어 작업에서 발생합니다.
  • 애플리케이션 오류: 애플리케이션 코드 내의 문제로 인해 발생합니다.
  • 네트워크 오류: 네트워킹 스택 내에서 발생하며 네트워킹 라이브러리에 의해 전달됩니다.
  • API 오류: Frame.io의 백엔드 서비스에 의해 생성됩니다.

각 오류 범주별로 특정 처리 방식을 고려해야 합니다. 이 가이드에서는 주로 API 오류에 초점을 맞추고 있지만, 다른 범주에 대한 일반적인 전략도 함께 다룹니다.

API 오류 반환 방법

Frame.io의 API는 두 가지 기본 메커니즘을 통해 오류를 전달합니다.

  • 상태 코드: 문제의 특성을 나타내는 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)은 예외입니다. 마찬가지로 404(Not Found) 응답은 4XX 코드임에도 불구하고 백엔드가 아닌 중간 서비스에 의해 생성될 수 있습니다.

예기치 않은 상태 코드가 발생하면 저희 팀에 알려주세요.

오류 페이로드 스키마

Frame.io는 두 가지 형식으로 오류 세부 정보를 반환합니다. simpledetailed. 오류 처리 로직은 두 형식을 모두 수용해야 합니다.

단순 오류 스키마

다음은 잘못된 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'

Response:

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

단순 스키마에는 오류 식별을 위한 단일 필드만 포함됩니다.

자세한 오류 스키마

비교를 위해, 다음은 적절한 승인 없는 요청의 예입니다.

$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

Response:

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 오류는 한 번 이상 재시도해야 합니다.

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를 발생시키는데, 이는 재시도에 가장 적합한 후보입니다.

확실치 않은 경우 한 번 재시도하세요

컴퓨팅 환경에서는 예측할 수 없는 문제가 발생할 수 있습니다. 치명적으로 보이는 오류라도 한 번 재시도해 볼 가치가 있습니다. 일시적인 시스템 상태, 하드웨어 이상(예: 우주 광선 비트 플립), 또는 드물게 발생하는 메모리 조건은 겉보기에 치명적인 오류를 일으킬 수 있으나 두 번째 시도에서 해결될 수 있습니다. 그러나 일부 오류는 다시 시도해서는 안 됩니다. 예를 들어, 에셋을 생성할 때 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초 동안 대기합니다.

백오프 지터

동일한 오류 조건에서 복구되는 여러 디바이스 간의 요청 동기화를 방지하기 위해 백오프 타이밍에 임의성(지터)을 추가하는 것이 좋습니다. 이는 서비스 중단 후 수많은 디바이스가 동시에 재시도할 때 발생하는 Thundering Herd 문제를 완화하는 데 도움이 됩니다. 0과 계산된 지연 시간의 절반 사이에 랜덤 오프셋을 추가하는 것이 좋은 방법입니다. 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: 개념적 오류 범주(descriptions 섹션에 자세히 설명됨) schema: 오류 페이로드 형식(simple 또는 detailed) retry: 재시도 권장 사항(여러 번 시도할 경우 yes, 한 번 시도할 경우 once, 치명적인 오류의 경우 no) 별표(*)는 descriptions 섹션에 자세히 설명된 특별 고려 사항을 나타냅니다.

Frame.io 오류 메시지

메시지오류 유형HTTP 코드스키마다시 시도
”access_denied”AccessDenied401simpleonce
”authorization_pending”AuthorizationPending400simple
”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”SlowDown400simple
”unauthorized_client”UnauthorizedClient401simpleyes*

Frame.io 상태 코드

HTTP 코드오류 유형다시 시도
400InvalidRequestonce
401UnauthorizedClientno
422InvalidContentTypeno
429SlowDown
500InternalServerError

AWS 오류

자세한 설명은 AWS 설명서를 참조하세요.

오류다시 시도
InternalError
OperationAborted
RequestTimeout
ServiceUnavailable
SlowDown
[기타 모든 오류]once
유사한 AWS 오류 구문 분석

AWS의 SlowDownServiceUnavailable은 모두 요청 속도 문제를 나타내며 Frame.io의 SlowDown 오류와 유사하게 취급하여 지수 백오프를 구현할 수 있습니다. 마찬가지로 AWS의 InternalError는 개념적으로 저희 API의 InternalServerError에 해당합니다.

설명

AccessDenied

사용자가 디바이스 페어링 중 인증을 거부할 때 반환됩니다.

AuthorizationPending

사용자가 아직 디바이스 페어링 코드를 입력하지 않았음을 나타냅니다. 디바이스 코드 응답에 지정된 interval 기간이 지난 후 폴링을 계속하세요.

ChannelPaused

에셋이 생성될 때 디바이스 채널이 일시 중지되었습니다. 이 에셋을 다시 업로드하려고 시도하지 마세요.

ExpiredToken

디바이스 페어링 코드가 만료되었습니다. 새 코드를 생성하고 페어링 프로세스를 다시 시작하세요.

InternalServerError

예기치 않은 백엔드 문제를 나타냅니다. 한 번 재시도하고, 조사할 수 있도록 500 오류를 저희 팀에 보고해 주세요. 일부 알려진 문제는 InvalidRequest를 반환해야 할 때 500 오류를 반환합니다.

  • 존재하지 않는 디바이스 채널에 업로드 시도
  • 잘못된 사용자 지정 청크 수 요청

InvalidArgument

페이로드 매개변수에 잘못된 값이 포함되어 있습니다. 매개변수 값이 API 기대치와 일치하는지 확인하세요.

InvalidContentType

요청의 Content-Type 헤더가 지원되지 않습니다. API는 일반적으로 다음을 허용합니다.

  • form/multipart(인증 엔드포인트 전용)
  • application/x-www-form-urlencoded(모든 엔드포인트)
  • application/json(비인증 엔드포인트)

InvalidClient

제공된 자격 증명(client_id, client_secret 등)이 인식되지 않았습니다. 통합 자격 증명을 확인하세요.

InvalidClientVersion

x-client-version 헤더가 중복되었거나 잘못된 시맨틱 버전을 포함하고 있습니다.

InvalidGrant

인증 부여 유형이 유효하지 않습니다. 올바른 값은 인증 가이드를 검토하세요.

InvalidRequest

요청 매개변수 또는 페이로드 형식이 잘못되었습니다. 필드 이름 및 값 형식을 확인하세요.

토큰 새로 고침 중에 받은 경우, 새로 고침 토큰이 만료되었으므로 인증 프로세스를 다시 시작해야 합니다.

SlowDown

요청 속도 제한을 초과했습니다. 후속 요청에 대해 지수 백오프를 구현하세요. 동일한 TCP 연결에서 여러 디바이스 코드 요청을 하면 이 오류가 발생할 수 있습니다. 각 페어링 요청에 대해 새 연결을 생성하세요.

UnauthorizedClient

일반적으로 만료되었거나 누락된 access_token을 나타냅니다. 이 오류가 발생하면 다시 시도하기 전에 토큰을 새로 고치세요.

토큰 새로 고침 중에 이 오류가 발생하면, 인증 프로세스를 다시 시작하고 사용자에게 다시 연결하도록 안내해야 합니다.

이 오류는 디바이스의 인증 범위를 벗어난 리소스에 액세스하거나 프로젝트에서 C2C 디바이스를 비활성화한 경우에도 발생할 수 있습니다. 인증 시 적절한 범위를 요청했는지 확인하세요.

토큰 새로 고침 중에 이 오류가 발생하면 사용자 개입을 통해 전체 인증 프로세스를 다시 시작해야 합니다.

다음 단계

궁금한 점이 있으면 저희 팀에 문의해 주시고, 고급 업로드 가이드로 진행하시기 바랍니다. 귀하의 통합 진행 과정을 지원할 수 있기를 기대합니다. 아직 확인하지 않으셨다면, 계속하기 전에 C2C 구현: 설정 가이드를 검토해 주세요. 인증 및 권한 부여 과정에서 획득한 access_token이 필요합니다. 이 가이드는 기본 업로드 가이드고급 업로드 가이드를 바탕으로 합니다.