Hooking 기능

ahttp_client 패키지에서 후킹이란 HTTP 요청이 있기 전과 후에 데이터를 처리하는 기능을 의미합니다. 세션 단위 후킹과 요청 단위 후킹으로 나누어집니다.

후킹 기능은 보통 아래와 같이 이용됩니다.

  • Before Hook (HTTP 요청 이전):
    • 인증과 같이 필요한 HTTP 구성요소를 설정합니다.

    • 인수가 올바르게 입력되었는지 검증합니다.

  • After Hook (HTTP 요청 이후):
    • 원본 데이터를 데이터클래스에 파싱합니다.

    • HTTP 상태 코드에 따라 적절한 조치합니다.

참고

여기서는 세션 단위의 before_request/after_request 후킹과 요청 단위의 before_hook/after_hook 데코레이터만 다룹니다. 재시도가 after_request 와 어떻게 맞물리는지는 재시도 (Retry) 문서를 참고하시고, 후킹에서 발생시키는 HTTPException 계층 구조가 궁금하다면 예외 (Exception) 을 보시면 됩니다.

세션 후킹(Session Hooking)

세션 단위의 후킹은 메소드를 오버라이딩하는 방식으로 만듭니다.

세션 단위의 후킹 예제
 1class GithubService(AsyncSession):
 2    def __init__(self, token: str):
 3        self._token = token  # Private Attribute
 4        super().__init__("https://api.github.com", aiohttp.ClientSession)
 5
 6    # overriding before_request method
 7    async def before_request(self, req_obj: RequestCore, path: str):
 8        req_obj.headers["Authorization"] = self._token
 9        req_obj.headers["Accepts"] = "application/vnd.github+json;"
10        return req_obj, path
11
12    # overriding after_request method
13    async def after_request(self, response: Response):
14        if response.status != 200:
15            raise HTTPException()
16        return response
17
18    @request("GET", "/users/{user}/repos")
19    async def list_repositories(
20        self, response: Response, user: Annotated[str, Path]
21    ) -> list[dict[str, Any]]:
22        return response.json()

GithubService 객체는 before_request 와 after_request 를 오버라이딩하여 정의합니다.

인증에 필요한 토큰을 비공개 속성 값으로 저장해두고, before_request 메소드에서 header에 그 값을 삽입합니다. list_repositories 메소드와 같이 GithubService 객체에 소속된 메소드가 호출되면, 필요한 HTTP 구성요소를 삽입하기 위해 before_request 메소드가 먼저 호출됩니다.

HTTP 요청이 끝나면, after_request 메소드가 호출되어 HTTP 상태 코드를 확인합니다. 상태 코드가 200 (OK)이 아니라면, 사전에 정의된 예외인 HTTPException 이 발생합니다 (예외 (Exception) 참고).

요청 후킹(Request Hooking)

요청 단위의 후킹은 데코레이터 메소드를 이용하여 생성하게 됩니다.

요청 단위의 후킹 예제
 1token = "GITHUB TOKEN"
 2
 3@AsyncSession.single_session("https://api.github.com", aiohttp.ClientSession)
 4@request("GET", "/repos/{user}/{repo}/topics")
 5async def repository_topic(
 6    session: AsyncSession,
 7    response: Response,
 8    user: Annotated[str, Path],
 9    repo: Annotated[str, Path]
10) -> list[str]:
11    return response.json()["names"]
12
13# before_hook method
14@repository_topic.before_hook
15async def before_hook(session: AsyncSession, req_obj: RequestCore, path: str):
16    req_obj.headers["Authorization"] = token
17    req_obj.headers["Accepts"] = "application/vnd.github+json;"
18    return req_obj, path
19
20# after_hook method
21@repository_topic.after_hook
22async def after_hook(session: AsyncSession, response: Response):
23    if response.status != 200:
24        raise HTTPException()
25    return response

깃허브 레포지토리의 주제를 가져오기 위해 repository_topic 메소드를 정의하였습니다. 그리고 같은 메소드의 before_hook 과 after_hook 데코레이터 메소드를 이용하여 후킹을 정의하였습니다.

before_hook 메소드에서는 필요한 구성 요소(인증키 등)를 HTTP 요청이 있기 전에 삽입하는 역할을 합니다.

after_hook 메소드는 요청 함수가 JSON 데이터를 파싱하기 전에 응답을 검증합니다.

경고

요청 단위의 after_hook 은 재시도 (Retry) 시도가 다 끝난 뒤에 실행됩니다. 그래서 retry_on 에 적힌 예외를 여기서 발생시켜도 재시도 대상이 되지는 않습니다.