본문 바로가기

파이썬 requests 로 외부 API 안전하게 호출하기 — 타임아웃·재시도·시크릿·검증

728x90

requests.get(url) 한 줄은 잘 돌아가는 것처럼 보이지만, 운영에 들어가면 네 가지 문제가 차례로 찾아옵니다. 상대 서버가 응답을 안 줘서 스레드가 영원히 기다리고, 잠깐의 5xx 에 배치가 통째로 실패하고, 코드에 적힌 API 키가 저장소에 남고, 인증서 경고를 verify=False 로 덮었다가 중간자 공격에 열립니다. 아래는 그 네 가지를 막는 기본 틀입니다.

1. 타임아웃은 선택이 아니다

requests 는 기본 타임아웃이 없습니다. 연결 타임아웃과 읽기 타임아웃을 튜플로 주세요.

resp = session.get(url, timeout=(3, 10))   # 연결 3초, 읽기 10초

값은 상대 서비스의 평소 응답 시간에 여유를 더해 정합니다. '넉넉하게 60초'는 장애 때 60초씩 묶이는 워커를 만듭니다.

2. 재시도는 제한된 횟수로, 간격을 늘리며

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(total=3, backoff_factor=0.5,
              status_forcelist=(500, 502, 503, 504),
              allowed_methods=frozenset({"GET", "HEAD"}))
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))

재시도는 멱등한 요청(GET·HEAD, 또는 멱등 키가 있는 POST)에만 겁니다. 결제처럼 두 번 실행되면 안 되는 요청을 재시도하면 더 큰 문제가 됩니다. 429(요청 과다)는 상대가 알려 주는 Retry-After 를 존중합니다.

3. 시크릿은 코드 밖에

import os
API_KEY = os.environ["PARTNER_API_KEY"]   # 없으면 KeyError 로 바로 실패하는 편이 낫다
headers = {"Authorization": f"Bearer {API_KEY}"}

로컬에서는 .env 파일(저장소에 올리지 않음), 운영에서는 시크릿 매니저나 배포 도구의 비밀값 기능을 씁니다. 로그에 헤더를 찍을 때는 Authorization 을 가립니다.

4. 인증서 검증은 끄지 않는다

사내 프록시나 자체 서명 인증서 때문에 SSL 오류가 나면 verify=False 대신 회사 CA 인증서를 받아 REQUESTS_CA_BUNDLE 환경변수나 verify="/path/ca.pem" 으로 지정합니다. 검증을 끄면 HTTPS 를 쓰는 의미가 사라집니다.

5. 응답은 믿기 전에 확인

resp.raise_for_status()
data = resp.json()
if not isinstance(data, dict) or "items" not in data:
    raise ValueError("unexpected response shape")

상태 코드만 보고 넘어가면 200 OK 에 에러 페이지 HTML 이 들어오는 경우를 놓칩니다. pydantic 같은 스키마 검증을 쓰면 더 분명합니다.

한 곳에 모아 두기

위 다섯 가지를 매번 적지 말고, 프로젝트에 http_client.py 하나를 두고 세션을 만들어 돌려 쓰세요. Session 은 연결을 재사용해 빠르기도 합니다.

728x90

댓글