FastAPI의 순수 Python 재구현
Ethan Miller
Product Engineer · Leapcell

WSGI에서 FastAPI와 유사한 라우팅 체계 구현
Python 웹 개발 영역에서 FastAPI는 효율적이고 간결한 라우팅 설계와 강력한 기능으로 개발자들에게 널리 사랑받고 있습니다. FastAPI는 기존의 WSGI (Web Server Gateway Interface)와는 다른 ASGI (Asynchronous Server Gateway Interface) 프로토콜을 기반으로 구축되었습니다. 이 글에서는 WSGI에서 시작하여 FastAPI와 유사한 라우팅 체계를 구현하는 방법과 WSGI 및 Uvicorn과 같은 핵심 개념과 그 상호 관계를 심층적으로 분석합니다.
I. WSGI, ASGI 및 Uvicorn 개념 분석
1.1 WSGI: 웹 서버 게이트웨이 인터페이스
WSGI는 웹 서버와 Python 웹 애플리케이션 간의 통신 규격을 정의하는 Python 웹 개발의 표준 인터페이스입니다. WSGI의 등장은 다양한 웹 서버와 웹 프레임워크 간의 호환성 문제를 해결하여 개발자가 함께 작동하지 못할까 걱정할 필요 없이 웹 서버와 프레임워크를 자유롭게 선택할 수 있도록 합니다.
WSGI는 웹 애플리케이션을 요청 정보(environ
)가 포함된 사전과 응답 상태 코드 및 헤더 정보를 보내기 위한 콜백 함수(start_response
)라는 두 개의 매개변수를 허용하는 호출 가능한 객체로 추상화합니다. 예를 들어, 간단한 WSGI 애플리케이션은 다음과 같이 작성할 수 있습니다.
def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [b'Hello, World!']
이 예에서 environ
은 요청 메서드, URL 및 HTTP 헤더와 같은 요청 관련 정보를 포함합니다. start_response
함수는 응답 상태 코드 및 헤더 정보를 설정하는 데 사용됩니다. 함수의 반환 값은 응답 본문입니다.
1.2 ASGI: 비동기 서버 게이트웨이 인터페이스
Python에서 비동기 프로그래밍이 부상하면서 특히 asyncio
라이브러리의 광범위한 사용으로 인해 기존 WSGI는 비동기 I/O 작업을 처리할 수 없게 되었습니다. ASGI가 탄생했습니다. WSGI의 확장으로서 동기 애플리케이션을 지원할 뿐만 아니라 비동기 요청을 효율적으로 처리하여 최신 웹 애플리케이션의 고성능 및 높은 동시성 요구 사항을 충족합니다.
ASGI는 또한 서버와 애플리케이션 간의 통신 프로토콜을 정의하지만 애플리케이션 호출 가능 객체는 비동기 메서드를 지원합니다. 간단한 ASGI 애플리케이션 예제는 다음과 같습니다.
async def simple_asgi_app(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ (b'content-type', b'text/plain') ] }) await send({ 'type': 'http.response.body', 'body': b'Hello, ASGI!' })
여기서 scope
는 요청 유형(예: http
)과 같은 정보를 포함합니다. receive
는 클라이언트가 보낸 데이터를 받는 데 사용됩니다. send
는 응답 데이터를 클라이언트에 보내는 데 사용되며 전체 프로세스는 비동기 작업을 지원합니다.
1.3 Uvicorn: ASGI 서버
Uvicorn은 Python 기반의 고성능 ASGI 서버로 ASGI 애플리케이션을 실행할 수 있으며 WSGI 애플리케이션과도 호환됩니다. Uvicorn은 uvloop
및 httptools
와 같은 라이브러리를 사용하여 효율적인 이벤트 루프 및 HTTP 구문 분석을 구현하여 ASGI의 비동기 장점을 최대한 활용하고 뛰어난 성능을 제공할 수 있습니다.
실제 개발에서는 Uvicorn을 사용하여 ASGI 또는 WSGI 애플리케이션을 시작할 수 있습니다. 예를 들어, 위의 ASGI 애플리케이션을 시작하려면 다음을 수행합니다.
uvicorn main:app --reload
여기서 main
은 애플리케이션 코드가 포함된 Python 모듈의 이름이고 app
은 모듈에 정의된 ASGI 애플리케이션 객체이며 --reload
매개변수는 코드가 변경될 때 애플리케이션을 자동으로 다시 로드하는 데 사용됩니다.
II. WSGI에서 FastAPI와 유사한 라우팅 체계 구현
2.1 라우팅 시스템의 기본 원리
FastAPI의 라우팅 시스템은 데코레이터를 통해 함수를 특정 URL 경로에 바인딩합니다. 경로와 일치하는 요청이 수신되면 해당 처리 함수가 호출되어 응답이 반환됩니다. WSGI에서 유사한 라우팅 시스템을 구현하는 핵심 아이디어는 요청의 URL 경로를 기반으로 해당 처리 함수를 찾고 호출하여 응답을 생성하는 것입니다.
URL 경로와 처리 함수 간의 매핑을 저장하기 위해 라우팅 테이블을 정의할 수 있습니다. 요청이 도착하면 라우팅 테이블에서 일치하는 경로를 찾은 다음 해당 처리 함수를 호출하여 응답을 생성합니다.
2.2 간단한 WSGI 라우팅 시스템 구현
아래에서는 FastAPI의 라우팅 기능을 시뮬레이션하기 위해 간단한 WSGI 라우팅 시스템을 점진적으로 구현합니다.
먼저 비어 있는 라우팅 테이블을 정의합니다.
route_table = {}
다음으로 함수를 URL 경로에 바인딩하고 라우팅 테이블에 추가하는 데코레이터를 만듭니다.
def route(path): def decorator(func): route_table[path] = func return func return decorator
그런 다음 요청 경로를 기반으로 해당 처리 함수를 찾아서 호출하는 WSGI 애플리케이션을 구현합니다.
def wsgi_app(environ, start_response): path = environ.get('PATH_INFO', '/') if path in route_table: response_body = route_table[path]() status = '200 OK' else: response_body = [b'404 Not Found'] status = '404 Not Found' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return response_body
이제 정의된 라우팅 데코레이터를 사용하여 처리 함수를 정의할 수 있습니다.
@route('/') def index(): return [b'Welcome to the index page!'] @route('/about') def about(): return [b'This is the about page.']
마지막으로 Uvicorn을 사용하여 이 WSGI 애플리케이션을 실행합니다.
uvicorn main:wsgi_app --reload
위의 단계를 통해 서로 다른 URL 경로에 따라 해당 콘텐츠를 반환할 수 있는 간단한 WSGI 라우팅 시스템을 구현하여 처음에는 FastAPI의 라우팅 기능을 시뮬레이션했습니다.
2.3 라우팅 시스템 개선
위의 라우팅 시스템은 기본 버전일 뿐이며 많은 단점이 있습니다. 예를 들어, 동적 라우팅(예: 매개변수화된 경로) 또는 요청 메서드 처리를 지원하지 않습니다. 아래에서는 이를 개선합니다.
동적 라우팅 지원
동적 라우팅을 지원하려면 정규식을 사용하여 경로를 일치시키고 경로에서 매개변수를 추출할 수 있습니다. re
모듈을 가져오고 라우팅 테이블과 라우팅 데코레이터를 수정합니다.
import re route_table = {} def route(path): def decorator(func): route_table[path] = func return func return decorator def dynamic_route(path_pattern): def decorator(func): route_table[path_pattern] = func return func return decorator
동시에 동적 라우팅을 처리하도록 WSGI 애플리케이션을 수정합니다.
def wsgi_app(environ, start_response): path = environ.get('PATH_INFO', '/') for pattern, handler in route_table.items(): if isinstance(pattern, str): if path == pattern: response_body = handler() status = '200 OK' break else: match = re.match(pattern, path) if match: args = match.groups() response_body = handler(*args) status = '200 OK' break else: response_body = [b'404 Not Found'] status = '404 Not Found' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return response_body
이제 동적 라우팅 처리 함수를 정의할 수 있습니다.
@dynamic_route(r'/user/(\d+)') def user_detail(user_id): return [f'User {user_id} detail page.'.encode()]
요청 메서드 지원
서로 다른 요청 메서드(예: GET 및 POST)를 지원하려면 각 경로에 해당하는 서로 다른 요청 메서드에 대한 처리 함수를 라우팅 테이블에 저장할 수 있습니다. 다음과 같이 라우팅 테이블과 라우팅 데코레이터를 수정합니다.
route_table = {} def route(path, methods=['GET']): def decorator(func): if path not in route_table: route_table[path] = {} for method in methods: route_table[path][method] = func return func return decorator
동시에 요청 메서드를 기반으로 해당 처리 함수를 호출하도록 WSGI 애플리케이션을 수정합니다.
def wsgi_app(environ, start_response): path = environ.get('PATH_INFO', '/') method = environ.get('REQUEST_METHOD', 'GET') if path in route_table and method in route_table[path]: response_body = route_table[path][method]() status = '200 OK' else: response_body = [b'404 Not Found'] status = '404 Not Found' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return response_body
이제 서로 다른 요청 메서드를 지원하는 처리 함수를 정의할 수 있습니다.
@route('/login', methods=['GET', 'POST']) def login(): return [b'Login page.']
위의 개선 사항을 통해 WSGI 라우팅 시스템이 더욱 완벽해져 동적 라우팅과 서로 다른 요청 메서드를 지원할 수 있어 FastAPI의 라우팅 기능에 더욱 가까워졌습니다.
III. 요약 및 전망
이 글에서는 WSGI의 기본 개념부터 시작하여 ASGI 및 Uvicorn에 대한 관련 지식을 소개하고 간단한 WSGI 라우팅 시스템을 점진적으로 구현하여 FastAPI의 라우팅 기능을 시뮬레이션합니다. 이 과정을 통해 웹 서버와 애플리케이션 간의 통신 프로토콜과 라우팅 시스템의 핵심 원리에 대한 심층적인 이해를 얻었습니다.
구현한 WSGI 라우팅 시스템은 여전히 FastAPI와는 거리가 멀지만 라우팅 시스템 구현을 이해하는 데 명확한 아이디어를 제공합니다. 앞으로 요청 본문을 구문 분석하고 응답 본문을 직렬화하는 기능을 추가하는 등 이 라우팅 시스템을 더욱 개선하여 실제 프로덕션 환경에서 사용되는 웹 프레임워크에 더 가깝게 만들 수 있습니다. 동시에 이 라우팅 시스템을 ASGI 환경으로 확장하여 비동기 프로그래밍의 장점을 최대한 활용하고 애플리케이션의 성능 및 동시성 처리 기능을 향상시키는 방법을 모색할 수도 있습니다.
위의 내용은 WSGI를 사용하여 라우팅 체계를 구현하는 과정을 자세히 설명합니다. 특정 부분을 더 자세히 확장해야 하거나 새로운 기능을 추가하고 싶다면 자유롭게 알려주세요.
Leapcell: 최고의 서버리스 웹 호스팅
Python 서비스를 배포하기에 가장 적합한 플랫폼으로 추천: Leapcell
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 간편하게 개발하세요.
🌍 무제한 프로젝트를 무료로 배포
사용한 만큼만 지불하세요. 요청도 없고, 요금도 없습니다.
⚡ 사용량에 따라 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공됩니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ