본문 바로가기
Python

Python - Http Retry 및 while 을 통한 재시도하기

by 올엠 2024. 3. 3.
반응형

요즘 REST API 참으로 많이 사용된다. 그런데 HTTP 호출을 사용하다보면, 받아주는 서버가 정상적으로 회신을 주지 않는 경우가 있다.

이러한 경우 어떻게 조치하는게 좋을지 Python에서 제공하는 모듈과 일반적인 방법에 대해서 정리해 본다.

1. while 문 사용

가장 일반적으로 사용되는 방식은 while 문을 이용한 특정 조건이 완료될 때 까지 반복 하는 방법이다.

while 문 내에 아래와 같이 1이 나올 때까지 실행하는 방식으로 조건이 완료될 때까지 실행할 수 있다.\

import random

while True:
    number = random.randint(0,1000)
    if number == 1:
        print('Bingo')
        break
    else:
        print(number)

이를 조금 웹 코드에 응용해보면, requests 모듈을 이용해서 status_code의 상태를 통해 반복문을 구사할 수 있다.

# This code is web request example, this code running don't want to get http status code 429.
# and i make 1 more condition that if meet 10 time about http status code 429, exit to while statement
import requests
import time

trycount = 0

while True:
    res = requests.get('https://httpstat.us/429')
    if res.status_code == 429:
        print('Too Many Requests!!!!')
        if trycount == 10:
            print('Stop!')
            break
        else:
            trycount += 1
            time.sleep(1)
    else:
        print(res)
        break

다만 while 문을 사용하는 경우에는 무한루프를 돌 수 있는 가능성이 있기 때문에 이 부분을 주의해서 코드를 작성하는것이 좋다.

대표적으로 최대 반복 횟수를 지정하고 해당 반복 횟수가 넘어가면 무조건 종료하도록 구성하는것이 제일 안전한 방식이 된다.

방금 얘기드린 방식은 일반적으로 사용하는 방식이라면 이제 Python에서만 사용가능하고 보다 간편한 방법에 대해서 소개해 보겠다.

 

2. HTTPAdapter 모듈 사용

HTTPAdapter는 requests 모듈에서 제공하는 기능으로 특정 HTTP 관련하여 예외상황이 발생하였을 때 최대 재시도 및 대기 시간등을 손쉽게 구성할 수 있다. 만약 위의 429 code에 대한 재시도를 정의 한다면 아래와 같이 정리할 수 있다.

import requests
from requests.adapters import HTTPAdapter
import logging # it's help to show debug log for retry HTTPadapter.

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG)

s = requests.Session()
adapter = HTTPAdapter(max_retries=10)
s.mount('https://', adapter)
r = s.get('https://httpstat.us/429')
print(r)

HTTPAdapter에는 몇가지 옵션을 추가로 제공하는데, 해당 옵션은 urllib3모듈의 Retry를 통해 보다 쉽게 정의해서 사용할 수 있다.

3. urllib3 - Retry

Retry 모듈에서 설정할 수 있는 옵션이 늘어나는데, 대표적으로 유용한 옵션 3가지를 여기에서 소개 하겠다.

  • total

   최대 시도 횟수를 정의할 수 있다. connect, read, redirect, status 등으로 나누어서 정의할 수 도 있지만, total을 넘어갈 수는 없다.

  • backoff_factor

    재시도를 진행할 때 대기시간을 주게된다. 기본적으로 제곱 방식으로 시간이 늘어나는 구조이다. 만약 2를 입력한다면 2초의 제곱으로서 점점 대기시간이 증가 된다. (2 → 2x2 → 2x2x2 → 2x2x2x2)

  • status_forcelist

    재시도를 진행하는 응답 상태를 늘릴 수 있다. 기본적으로는 응답코드는 429 인 경우에 재시도를 진행한다. (Too Many Requests)

그럼 Retry를 통해서 코드를 구성해보자.

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

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG)

s = requests.Session()

adapter = HTTPAdapter(max_retries=50)

s.mount('https://', adapter)
r = s.get('https://httpstat.us/502')
print(r)

retries = Retry(total=50,
                backoff_factor=0.2,
                status_forcelist=[ 500, 502, 503, 504 ]) # 추가로 재시도 하고자 하는 HTTP 코드를 입력.

adapter = HTTPAdapter(retries)
s.mount('https://', adapter)
r = s.get('https://httpstat.us/502')
print(r)

만약에 이를 함수 기반으로 만들어 사용하고자 한다면 다음과 같이 이용할 수 있다.

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

def requests_retry_session(
    retries=10,
    backoff_factor=0.3,
    status_forcelist=(500, 502, 504),
    session=None,
):

    session = session or requests.Session()
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=status_forcelist,
    )

    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session

r = requests_retry_session().('https://httpstat.us/502')
 
반응형