///
Search
✏️

알고 돌리면 조금 더 재미있는 ASGI와 FastAPI

created
2025/06/21 09:48
last edited
2025/06/22 06:19
difficulty
문과: 어려움
이과: 보통
1 more property
Python을 사용하는 개발자라면 한 번쯤은 FastAPI로 API를 만들고, uvicorn 명령어로 서버를 띄워본 경험이 있을 것이다. 나도 빠르게 API 서버를 만들 때 FastAPI를 많이 사용하는데, 서버를 띄울 때마다 FastAPI 소스 코드를 작성했을 뿐인데, uvicorn 은 뜬금없이 왜 나온건가 궁금했다.
이 궁금증을 풀다 보면 ASGI라는 단어를 계속 마주치게 된다. ASGI를 검색하면 “비동기 웹 서버와 웹 애플리케이션 간의 표준 인터페이스” 같은 길고 무서운 정의가 나온다. ‘에이… 내가 백엔드 개발자도 아니고’ 하는 마음에 그냥 뭐 애플리케이션 구동 엔진 같은거겠지 하고 넘어가곤 했지만 최근 조금 더 깊이 알아보아야 할 필요를 느꼈고, 그래서 글로 차분히 다시 정리해보게 되었다.
ASGI를 조금 더 잘 이해하기 위해 우선 “비동기 웹 서버와 웹 애플리케이션 간의 표준 인터페이스”라는 긴 문장 속에서 ‘비동기’라는 개념을 잠시 뒤로 미뤄두고 ‘웹 서버와 웹 애플리케이션 간의 표준 인터페이스’라는 부분을 먼저 이해해 보자. 어느정도 윤곽이 드러날 때 비동기 개념을 살짝 얹어 보기만 해도 ASGI 기반의 프레임워크를 사용하는 일에는 지장이 없을 정도로 충분하다.
ASGI의 정의에서도 드러나듯 ASGI는 두 개의 이해관계 구성원: ASGI 서버(ref1)ASGI 애플리케이션으로 구성된다(ref2). 게이트 인터페이스가 뭐고 어쩌고 하는 개념보다는 그냥 코드 조각 하나를 두고 이해하는 편이 훨씬 낫다. 아래 네 줄짜리 코드를 보자.
async def application(scope, receive, send): event = await receive() ... await send({"type": "websocket.send", ...})
Python
복사
FastAPI는 ASGI가 정의하는 이해관계 구성원 표현상 ASGI 애플리케이션이다. ASGI 애플리케이션이 되려면 위 함수를 구현하면 된다. 다시말해 “FastAPI는 ASGI 스펙을 준수하는 웹 프레임워크다.” 라는 말은 FastAPI가 위 함수를 구현했다는 말이다. FastAPI는 scope, receive, send라는 것을 전달받아 어떻게 처리를 해서 send({"type": "websocket.send", ...})를 리턴하는 방법을 정의했다. 이들 각각에 실제로 어떤 값이 들어 있는지, 지금은 별로 중요하지 않다. 모종의 약속된 값을 주고받았고, 위 코드 조각이 ASGI 서버와 ASGI 클라이언트가 어떤 방식으로 데이터를 주고받을지에 대한 규칙(인터페이스)이라는 점이 중요하다. 이렇게 약속대로 구현한 application 함수를 실행하는 주체가 바로 ASGI 서버다.
Uvicorn 같은 것들이 바로 ASGI 서버 구현체다. ASGI 서버는 클라이언트와 네트워크 연결을 관리하고, 요청을 받아 애플리케이션에 전달하는 역할을 한다. 웹 애플리케이션은 실제 요청을 처리해 응답을 생성해서 ASGI 서버 구현체로 넘기면 ASGI 서버가 클라이언트에게 최종 응답을 전송한다.
FastAPI는 async def application(scope, receive, send)을 어디 숨겨 두었을까? 바로 FastAPI의 부모 클래스 Starlette이다. 아래는 Starlette 소스코드인데, __call__ 을 보자.
class Starlette: """Creates an Starlette application.""" def __init__( self: AppType, debug: bool = False, routes: typing.Sequence[BaseRoute] | None = None, middleware: typing.Sequence[Middleware] | None = None, exception_handlers: typing.Mapping[typing.Any, ExceptionHandler] | None = None, on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None, on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None, lifespan: Lifespan[AppType] | None = None, ) -> None: ... async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self if self.middleware_stack is None: self.middleware_stack = self.build_middleware_stack() await self.middleware_stack(scope, receive, send)
Python
복사
FastAPI와 Uvicorn은 각각 ASGI 애플리케이션과 서버 역할을 한다고 했다. Uvicorn뿐 아니라 Daphne, Hypercorn 같은 다른 ASGI 서버도 결국 application(scope, receive, send)을 호출하고 반환값을 처리해 클라이언트에 응답한다. uvicorn main:app 이라는 명령어가 하는 일은, ASGI 웹 서버 Uvicorn에게 async def application(scope, receive, send)을 구현한 ASGI 애플리케이션이 main.py파일에 위치한 변수 app임을 알려주는 것이다. ASGI 웹 서버 Hypercorn에게 알려주는 명령어hypercorn main:app로 동일하다. 반대도 마찬가지다. 꼭 FastAPI가 아니더라도 ASGI 애플리케이션 인터페이스를 구현하는 프레임워크는 그 어떤 ASGI 서버에든 끼워 사용할 수 있다(ref3).
마지막으로 ASGI의 정의 “비동기 웹 서버와 웹 애플리케이션 간의 표준 인터페이스” 에서 “비동기”의 의미를 간단히 이해해 보자. 사실 과거에도 웹 서버와 웹 애플리케이션 간의 표준 인터페이스가 있었다. 그 이름은 WSGI였다.
def application(environ, start_response): ... return [response_body]
Python
복사
함수 인자가 조금 다르다는 것을 제외하고, WSGI에서 구현/호출해야 하는 함수가 비동기 함수가 아닌 동기 함수였다는 점이 가장 큰 차이라고 볼 수 있다(ref1). WSGI는 동기적 함수를 인터페이스로 사용하기 때문에 비동기 요청을 받을 수 없었다. 당시 파이썬으로 구현한 웹 애플리케이션은 제한된 목적으로만 사용될 수 있었을 것이다. 오늘날 ASGI를 지원하는 ASGI 서버와 애플리케이션은 비동기 함수로 작성되어 I/O 작업 중에 다른 요청을 처리할 수 있어 동시성(concurrency)이 크게 향상되었다.
글을 쓰는 데 참고한 자료입니다.
글을 쓰는 데 반영된 생각들입니다.
1.
작성 중입니다.
이 글은 다음 글로 이어집니다.
1.
작성 중입니다.
바로가기
다빈치 작업실 - 블로그 홈
생각 완전체 - 포스트