Starlette 공개: 강력한 웹 서비스를 위한 FastAPI의 ASGI 툴킷 심층 분석
Wenhao Wang
Dev Intern · Leapcell

Starlette 공개: 강력한 웹 서비스를 위한 FastAPI의 ASGI 툴킷 심층 분석
빠르게 진화하는 웹 개발 환경에서 성능과 확장성은 무엇보다 중요합니다. 전통적으로 다재다능함으로 칭찬받아온 Python은 비동기 서버 게이트웨이 인터페이스(ASGI)와 이를 기반으로 구축된 프레임워크의 등장 덕분에 고성능 웹 애플리케이션에서 다시금 주목받고 있습니다. 이들 중 FastAPI는 속도, 직관적인 API 디자인, 자동 문서화로 개발자들을 사로잡으며 혁신적인 솔루션으로 부상했습니다. 하지만 FastAPI의 우아한 외관 아래에는 강력하고 유연한 툴킷이 숨어 있습니다. 바로 Starlette입니다. Starlette을 이해하는 것은 단순한 학문적 연습이 아닙니다. 이는 FastAPI가 어떻게 놀라운 효율성을 달성하는지에 대한 더 깊은 통찰력을 얻고 처음부터 사용자 정의 고성능 ASGI 애플리케이션을 구축할 수 있는 잠재력을 발휘할 기회입니다. 이 글은 Starlette의 막을 걷어내고 라우팅, 미들웨어, 응답과 같은 핵심 구성 요소를 분석하며, 이러한 요소들이 어떻게 현대 Python 웹 서비스의 기반을 형성하는지 설명하는 것을 목표로 합니다.
비동기 웹 개발의 기초
Starlette의 세부 사항으로 들어가기 전에, 그 작동을 뒷받침하는 몇 가지 기본 개념을 이해하는 것이 중요합니다.
ASGI (Asynchronous Server Gateway Interface): ASGI는 비동기 요청을 처리하도록 설계된 WSGI의 정신적 후계자입니다. 비동기 Python 웹 서버(Uvicorn 또는 Hypercorn와 같은)와 비동기 Python 웹 애플리케이션 또는 프레임워크 간의 표준 인터페이스를 정의합니다. 이 인터페이스는 오래 지속되는 연결, WebSocket 및 HTTP/2 기능을 허용하여 현대적인 실시간 애플리케이션을 현실로 만듭니다.
Starlette: Starlette은 고성능 비동기 웹 서비스를 구축하기 위해 명시적으로 설계된 경량 ASGI 프레임워크/툴킷입니다. 라우팅, 미들웨어, 요청/응답 처리와 같은 핵심 기능을 제공하며, 풀 스택 프레임워크의 오버헤드 없이 속도와 유연성이 필요한 애플리케이션을 위한 견고한 기반을 제공합니다. FastAPI는 Starlette을 많이 활용하며, 데이터 유효성 검사/직렬화(Pydantic) 및 자동 API 문서화(OpenAPI/JSON Schema)와 같은 강력한 기능을 추가합니다.
라우팅: 웹 개발에서 라우팅은 URL 경로와 HTTP 메서드에 따라 들어오는 요청을 적절한 핸들러 함수로 전달하는 프로세스입니다. Starlette의 라우팅 시스템은 효율적이고 유연하도록 설계되어 개발자가 복잡한 URL 패턴을 정의할 수 있습니다.
미들웨어: 미들웨어 구성 요소는 메인 애플리케이션 핸들러에 도달하기 전에 들어오는 요청과 핸들러를 떠난 후 나가는 응답을 처리하는 함수 또는 클래스입니다. 이는 개별 라우트 핸들러를 어지럽히지 않고 인증, 로깅, 오류 처리 또는 CORS 정책과 같은 관통 관심사를 추가하는 강력한 메커니즘을 제공합니다.
응답: 요청을 처리한 후 웹 애플리케이션은 클라이언트에 응답을 보내야 합니다. Starlette은 다양한 콘텐츠 유형과 시나리오를 효율적으로 처리하기 위해 다양한 응답 클래스(PlainTextResponse
, JSONResponse
, HTMLResponse
, StreamingResponse
, FileResponse
등)를 제공합니다.
Starlette 실제 적용: 라우팅, 미들웨어, 응답 설명
Starlette의 강점은 모듈성과 성능 중심 설계에 있습니다. 실제 예제를 통해 핵심 기능을 살펴보겠습니다.
라우팅: 요청을 목적지로 안내
Starlette의 라우팅 시스템은 표현력이 풍부하고 간단합니다. URL 경로와 HTTP 메서드를 비동기 핸들러 함수와 연결하여 라우트를 정의합니다.
# main.py from starlette.applications import Starlette from starlette.responses import PlainTextResponse, JSONResponse from starlette.routing import Route async def homepage(request): return PlainTextResponse("Hello, world!") async def user_detail(request): user_id = request.path_params['user_id'] return JSONResponse({"message": f"User ID: {user_id}"}) routes = [ Route("/", homepage), Route("/users/{user_id:int}", user_detail), # 타입 주석이 있는 경로 매개변수 ] app = Starlette(routes=routes)
이를 실행하려면 일반적으로 Uvicorn과 같은 ASGI 서버를 사용합니다: uvicorn main:app --reload
.
/
에 접근하면 "Hello, world!"가 반환됩니다./users/123
에 접근하면{"message": "User ID: 123"}
이 반환됩니다.
user_id
가 경로에서 자동으로 추출되어 request.path_params
에서 사용 가능함을 알 수 있습니다. Starlette은 더 고급 라우팅 요구 사항을 위해 정규식과 사용자 정의 경로 변환기도 지원합니다.
미들웨어: 요청/응답 가로채기 및 향상
미들웨어는 강력한 웹 애플리케이션을 구축하는 데 중요한 측면입니다. Starlette은 정의된 순서대로 실행되는 여러 미들웨어 구성 요소를 쌓을 수 있도록 합니다.
간단한 로깅 미들웨어와 CORS 미들웨어를 구현해 보겠습니다.
# main.py (계속) import time from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.responses import PlainTextResponse, JSONResponse from starlette.applications import Starlette from starlette.routing import Route async def homepage(request): return PlainTextResponse("Hello, world!") async def user_detail(request): user_id = request.path_params['user_id'] return JSONResponse({"message": f"User ID: {user_id}"}) routes = [ Route("/", homepage), Route("/users/{user_id:int}", user_detail), ] async def custom_logging_middleware(request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) print(f"Request: {request.method} {request.url.path} - Processed in {process_time:.4f}s") return response middleware = [ Middleware( CORSMiddleware, allow_origins=["*"], # 실제 앱에서는 이를 제한하십시오 allow_methods=["*"], allow_headers=["*"], ), Middleware(custom_logging_middleware), ] app = Starlette(routes=routes, middleware=middleware)
이제 요청을 하면:
CORSMiddleware
는 교차 출처 요청을 허용하는 데 필요한 CORS 헤더를 추가합니다.custom_logging_middleware
는 요청 메서드와 경로를 로깅하고,call_next
(실제 라우트 핸들러 및 후속 미들웨어를 실행)를 래핑한 다음, 처리 시간을 로깅하고 응답에 사용자 정의 헤더를 추가합니다.
미들웨어는 전체 애플리케이션에 대해 전역적으로 정의하거나 더 세밀한 제어를 위해 라우트별로 지정할 수 있습니다.
응답: 출력 만들기
Starlette은 다양한 데이터 형식과 시나리오를 처리하기 위한 풍부한 응답 클래스 세트를 제공합니다.
# main.py (계속) from starlette.responses import ( PlainTextResponse, JSONResponse, HTMLResponse, RedirectResponse, StreamingResponse, FileResponse ) import io # ... (라우트 및 미들웨어 정의는 동일하게 유지) async def serve_html(request): content = "<h1>Welcome to Starlette!</h1><p>This is an HTML response.</p>" return HTMLResponse(content) async def serve_file(request): # 디렉토리와 동일한 디렉토리에 있는 "example.txt"와 같은 정적 파일을 가정합니다. # 실제 애플리케이션의 경우 starlette.staticfiles.StaticFiles를 고려하십시오. return FileResponse("example.txt", media_type="text/plain") async def redirect_example(request): return RedirectResponse(url="/") async def stream_data(request): async def generate_bytes(): for i in range(5): yield f"Line {i+1}\n".encode("utf-8") await asyncio.sleep(0.5) # 약간의 작업 시뮬레이션 return StreamingResponse(generate_bytes(), media_type="text/plain") routes.extend([ Route("/html", serve_html), Route("/file", serve_file), Route("/redirect", redirect_example), Route("/stream", stream_data), ]) app = Starlette(routes=routes, middleware=middleware)
HTMLResponse
: HTML 콘텐츠를 반환합니다.FileResponse
: 파일 시스템에서 정적 파일을 제공합니다.RedirectResponse
: 클라이언트를 다른 URL로 리디렉션하는 307(임시 리디렉션) 또는 308(영구 리디렉션) 응답을 발급합니다.StreamingResponse
: 전체 콘텐츠를 메모리에 로드하지 않고 청크 단위로 데이터를 보내려는 대용량 파일, 실시간 데이터 피드 또는 모든 시나리오에 이상적입니다.generate_bytes
비동기 생성기는 바이트 청크를 생성합니다.
고급 개념: 예외 처리 및 수명 주기 이벤트
Starlette은 전역 예외 처리 및 애플리케이션 시작/종료 이벤트를 관리하기 위한 메커니즘도 제공합니다.
# main.py (더 이어서) import asyncio from starlette.exceptions import HTTPException from starlette.responses import JSONResponse # ... (이전 가져오기, 라우트, 미들웨어) ... async def custom_exception_handler(request, exc): if isinstance(exc, HTTPException): return JSONResponse({"detail": exc.detail}, status_code=exc.status_code) return JSONResponse({"detail": "An unexpected error occurred"}, status_code=500) async def startup_event(): print("Application is starting up...") # 데이터베이스 연결, 모델 로드 등 수행 async def shutdown_event(): print("Application is shutting down...") # 데이터베이스 연결 닫기, 리소스 해제 exception_handlers = { HTTPException: custom_exception_handler, 500: custom_exception_handler, # 일반 500 오류 잡기 } app = Starlette( routes=routes, middleware=middleware, exception_handlers=exception_handlers, on_startup=[startup_event], on_shutdown=[shutdown_event] ) # 예외를 발생시키는 예제 라우트 async def trigger_error(request): raise HTTPException(status_code=400, detail="This is a bad request!") routes.append(Route("/error", trigger_error))
exception_handlers
: 특정 예외 유형 또는 HTTP 상태 코드에 대한 사용자 정의 핸들러를 정의할 수 있습니다./error
에 접근하면custom_exception_handler
가HTTPException
을 JSON 응답으로 형식화합니다.on_startup
및on_shutdown
: 이 목록은 ASGI 서버가 애플리케이션을 시작하고 중지할 때 비동기 함수를 실행하며, 각각 실행됩니다. 이는 데이터베이스 연결과 같은 리소스를 설정하고 해제하는 데 완벽합니다.
Starlette 장점: FastAPI를 구동하는 이유
FastAPI가 Starlette에 의존하는 것은 우연이 아닙니다. Starlette은 다음을 제공합니다:
- 비동기 우선 디자인:
async/await
를 위해 처음부터 구축되어 I/O 바운드 작업에 대한 최적의 성능을 보장합니다. - 최소한의 코어: 필수 웹 구성 요소에 중점을 두어 FastAPI가 자체 강력한 기능(Pydantic과 같은)을 통합할 수 있도록 하는 편견 없는 구조로 유연성을 제공합니다.
- 성능: 효율적인 디자인은 직접적으로 높은 처리량과 낮은 지연 시간으로 이어지며, 현대 API에 매우 중요합니다.
- 확장성: 미들웨어와 라우팅 시스템은 고도로 확장 가능하며 타사 라이브러리 또는 사용자 정의 로직을 쉽게 통합할 수 있습니다.
- Pythonic API: Starlette은 Python 관용구를 채택하여 Python 개발자에게 즐거운 작업 환경을 제공합니다.
FastAPI는 Starlette의 직접적인 사용을 많이 추상화하지만, Starlette을 이해하면 개발자가 더 복잡하거나 사용자 정의된 시나리오에 대한 해당 기능을 활용하고, 문제를 더 효과적으로 디버깅하거나, FastAPI의 오버헤드가 필요하지 않은 경우 자체 경량 ASGI 애플리케이션을 구축할 수 있습니다.
결론
Starlette은 고성능 웹 개발에서 Python의 진화하는 능력을 입증합니다. 라우팅, 미들웨어 및 응답 처리를 위한 깔끔하고 비동기 우선 기반을 제공함으로써 FastAPI를 선두로 이끌었을 뿐만 아니라 확장 가능한 ASGI 애플리케이션을 구축하려는 모든 사람에게 강력한 툴킷을 제공합니다. 최소한의 디자인이지만 강력한 디자인은 효율성을 보장하면서 다양한 웹 서비스 문제를 해결하는 데 필요한 유연성을 제공합니다. 현대 Python 웹 개발을 마스터하려는 모든 사람에게 Starlette의 우아한 아키텍처에 대한 심층적인 탐구는 매우 가치 있는 여정입니다. 오늘날 가장 성능이 뛰어난 Python API 중 상당수를 조용히 뒷받침하는 고성능 엔진이라고 할 수 있습니다.