Gin과 FastAPI에서의 미들웨어 실행 과정 심층 분석
Daniel Hayes
Full-Stack Engineer · Leapcell

소개
백엔드 개발이라는 복잡한 세계에서 견고하고 확장 가능한 웹 서비스를 구축하는 것은 종종 미들웨어의 전략적 사용에 크게 좌우됩니다. 이러한 강력한 구성 요소는 요청 사전 처리, 응답 후 처리, 인증, 로깅, 오류 관리 등 훨씬 더 많은 것을 수행할 수 있는 유연한 방법을 제공합니다. 이 모든 것이 핵심 비즈니스 로직을 복잡하게 만들지 않으면서 가능합니다. 그러나 이러한 미들웨어들이 실행되는 정확한 순서와 기본 요청-응답 주기와의 상호 작용을 파악하는 것은 개발자에게 중요한 장애물이 될 수 있으며, 이는 미묘한 버그나 비효율적인 아키텍처로 이어질 수 있습니다. 이 파이프라인을 이해하는 것은 단순한 학문적 연습이 아닙니다. 디버깅, 성능 최적화, 애플리케이션의 보안 및 안정성 보장에 매우 중요합니다. 이 기사에서는 Go용 Gin과 Python용 FastAPI라는 두 가지 인기 있는 웹 프레임워크에서의 미들웨어 실행 메커니즘을 깊이 있게 살펴보고, 각 스택을 통한 작동 순서와 요청 및 응답의 여정을 명확히 설명할 것입니다.
미들웨어 및 요청-응답 흐름에 대한 심층 분석
Gin과 FastAPI의 구체적인 내용을 살펴보기 전에, 미들웨어와 HTTP 요청-응답 주기를 뒷받침하는 핵심 개념에 대한 공통된 이해를 확립해 봅시다.
핵심 용어
- 미들웨어(Middleware): 웹 서버와 애플리케이션 사이에 위치하여 들어오는 요청과 나가는 응답을 처리하는 소프트웨어 구성 요소입니다. 각 미들웨어는 일반적으로 특정 작업을 수행한 다음 제어권을 체인의 다음 미들웨어 또는 최종 라우트 핸들러로 전달합니다.
- 요청(Request): 클라이언트(예: 웹 브라우저, 모바일 앱)가 서버로 리소스를 요청하거나 작업을 수행하기 위해 보내는 HTTP 메시지입니다. 여기에는 메서드(GET, POST 등), URL, 헤더 및 잠재적으로 본문이 포함됩니다.
- 응답(Response): 서버가 요청에 대한 응답으로 클라이언트로 다시 보내는 HTTP 메시지입니다. 여기에는 상태 코드, 헤더 및 본문(예: HTML, JSON)이 포함됩니다.
- 컨텍스트(Context): 단일 HTTP 요청-응답 주기에 대한 모든 정보를 캡슐화하는 객체입니다. 여기에는 종종 요청 자체, 응답을 작성하는 메서드, 미들웨어 간의 데이터를 저장하고 검색하는 메커니즘이 포함됩니다.
- 라우트 핸들러(Route Handler) 또는 엔드포인트 함수(Endpoint Function): 요청이 정의된 라우트와 일치할 때 실행되는 특정 함수 또는 메서드입니다. 이곳에서 일반적인 핵심 비즈니스 로직이 상주합니다.
- 책임 연쇄 패턴(Chain of Responsibility Pattern): 미들웨어는 종종 이 디자인 패턴을 구현하며, 여기서 요청은 핸들러 체인을 따라 순차적으로 전달되고, 각 핸들러는 특정 논리를 행사합니다.
Gin 미들웨어 실행 및 요청/응답 흐름
Go로 작성된 고성능 HTTP 웹 프레임워크인 Gin은 강력하고 직관적인 미들웨어 시스템을 활용합니다.
Gin 미들웨어의 원칙
Gin에서 미들웨어 함수는 *gin.Context
객체를 받는 http.HandlerFunc
와 호환되는 함수입니다. 일반적으로 다음과 같이 보입니다.
func MyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 요청이 진행되기 전 사전 처리 로직 log.Println("Before request:", c.Request.URL.Path) // 다음 미들웨어/핸들러로 제어권 전달 c.Next() // 핸들러에서 반환된 후 응답 후 처리 로직 log.Println("After request:", c.Request.URL.Path, "Status:", c.Writer.Status()) } }
c.Next()
호출은 매우 중요합니다. 이 함수를 통해 현재 미들웨어는 체인의 다음 미들웨어/핸들러로 제어권을 넘길 수 있습니다. c.Next()
가 호출되지 않으면 요청은 현재 미들웨어에서 중단되며, 이후의 미들웨어나 라우트 핸들러는 실행되지 않습니다.
실행 순서 예시
다음 Gin 애플리케이션을 고려해 봅시다.
package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" ) // LoggerMiddleware는 요청 시작 및 종료 시간을 기록합니다. func LoggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { startTime := time.Now() log.Printf("LoggerMiddleware: Starting request for %s %s", c.Request.Method, c.Request.URL.Path) c.Next() // 체인의 다음 핸들러로 제어권 전달 duration := time.Since(startTime) log.Printf("LoggerMiddleware: Finished request for %s %s in %v. Status: %d", c.Request.Method, c.Request.URL.Path, duration, c.Writer.Status()) } } // AuthenticationMiddleware는 유효한 헤더를 확인합니다. func AuthenticationMiddleware() gin.HandlerFunc { return func(c *gin.Context) { log.Printf("AuthenticationMiddleware: Checking authentication for %s", c.Request.URL.Path) token := c.GetHeader("Authorization") if token == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"}) log.Printf("AuthenticationMiddleware: Unauthorized access to %s", c.Request.URL.Path) return // 필수: 승인되지 않은 요청의 경우 여기서 처리를 중지합니다. } // 실제 앱에서는 토큰을 검증합니다. c.Set("user_id", "some_user_id_from_token") // 다운스트림 핸들러를 위해 컨텍스트에 데이터 저장 log.Printf("AuthenticationMiddleware: Authorized access to %s", c.Request.URL.Path) c.Next() } } func main() { router := gin.Default() // 모든 라우트에 적용되는 전역 미들웨어 router.Use(LoggerMiddleware()) // 특정 미들웨어가 포함된 라우트 그룹 authorized := router.Group("/admin") authorized.Use(AuthenticationMiddleware()) // /admin 그룹에 특정한 미들웨어 { authorized.GET("/dashboard", func(c *gin.Context) { userID := c.MustGet("user_id").(string) // 컨텍스트에서 데이터 검색 log.Printf("DashboardHandler: User %s accessing dashboard", userID) c.JSON(http.StatusOK, gin.H{"message": "Welcome to the admin dashboard", "user": userID}) }) } router.GET("/public", func(c *gin.Context) { log.Printf("PublicHandler: Accessing public endpoint") c.JSON(http.StatusOK, gin.H{"message": "This is a public endpoint"}) }) log.Println("Starting Gin server on :8080") if err := router.Run(":8080"); err != nil { log.Fatalf("Gin server failed to start: %v", err) } }
/admin/dashboard
에 대한 실행 흐름:
- 요청 도착.
LoggerMiddleware
:log.Printf("LoggerMiddleware: Starting request...")
가 실행됩니다.LoggerMiddleware
에서c.Next()
호출. 제어권이 다음 전역 또는 그룹별 미들웨어로 전달됩니다.AuthenticationMiddleware
:log.Printf("AuthenticationMiddleware: Checking authentication...")
가 실행됩니다.- 승인된 경우:
log.Printf("AuthenticationMiddleware: Authorized...")
가 실행됩니다.c.Next()
가 호출됩니다. 제어권이 라우트 핸들러로 전달됩니다. authorized.GET("/dashboard", ...)
(라우트 핸들러):log.Printf("DashboardHandler: User %s accessing dashboard")
가 실행됩니다.c.JSON
이 응답을 작성합니다.- 라우트 핸들러 반환. 제어권이
c.Next()
호출 스택을 따라 위로 역류합니다. AuthenticationMiddleware
(후처리): 이 예시에는 명시적인 후처리가 없지만,c.Next()
이후에 로직이 배치되었다면 여기서 발생합니다.AuthenticationMiddleware
반환.LoggerMiddleware
(후처리):log.Printf("LoggerMiddleware: Finished request...")
가 실행되어/admin/dashboard
에 대한 최종 상태를 관찰합니다.- 응답이 클라이언트로 전송됩니다.
/public
에 대한 실행 흐름:
- 요청 도착.
LoggerMiddleware
:log.Printf("LoggerMiddleware: Starting request...")
가 실행됩니다.LoggerMiddleware
에서c.Next()
호출. 제어권이/public
에 대한 라우트 핸들러로 전달됩니다 (그룹별 미들웨어 없음).router.GET("/public", ...)
(라우트 핸들러):log.Printf("PublicHandler: Accessing public endpoint")
가 실행됩니다.c.JSON
이 응답을 작성합니다.- 라우트 핸들러 반환. 제어권이 위로 역류합니다.
LoggerMiddleware
(후처리):log.Printf("LoggerMiddleware: Finished request...")
가 실행됩니다.- 응답이 클라이언트로 전송됩니다.
Gin의 핵심: 미들웨어는 서로를 감쌉니다. c.Next()
가 호출되면 실행 흐름이 일시 중지되고, 스택을 더 깊이 파고들어 후속 미들웨어/핸들러를 실행한 다음, 현재 미들웨어의 후처리 로직을 완료하기 위해 제어권이 돌아옵니다. c.AbortWithStatusJSON
은 체인을 조기에 종료하고 핸들러의 추가 실행을 방지하는 데 중요합니다.
FastAPI 미들웨어 실행 및 요청/응답 흐름
FastAPI는 Python 3.7+ 기반의 현대적이고 빠른(고성능) API 구축용 웹 프레임워크로, 표준 Python 타입 힌트를 기반으로 합니다. 이 프레임워크도 강력한 미들웨어 시스템을 갖추고 있습니다.
FastAPI 미들웨어의 원칙
FastAPI 미들웨어 함수는 비동기적이며 일반적으로 @app.middleware("http")
데코레이터를 사용하여 정의됩니다. 이들은 request
객체와 call_next
함수를 인수로 받습니다.
from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from starlette.types import ASGIApp class MyMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 요청이 진행되기 전 사전 처리 로직 print(f"MyMiddleware: Before request for {request.url.path}") response = await call_next(request) # 다음 미들웨어/핸들러로 제어권 전달 # 핸들러에서 반환된 후 응답 후 처리 로직 print(f"MyMiddleware: After request for {request.url.path}, Status: {response.status_code}") return response
await call_next(request)
는 Gin의 c.Next()
에 해당하는 FastAPI 버전입니다. 이 함수는 비동기적으로 다음 미들웨어나 라우트 핸들러를 호출합니다. 다운스트림 구성 요소의 응답은 반환되어 현재 미들웨어가 자체 응답을 반환하기 전에 후처리를 수행할 수 있도록 합니다.
실행 순서 예시
FastAPI 애플리케이션으로 설명해 보겠습니다.
from fastapi import FastAPI, Request, Response, status from starlette.middleware.base import BaseHTTPMiddleware import time import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI() # @app.middleware를 사용한 전역 미들웨어 @app.middleware("http") async def logger_middleware(request: Request, call_next): start_time = time.time() logger.info(f"LoggerMiddleware: Request started for {request.method} {request.url.path}") response = await call_next(request) # 제어권 양도 process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) logger.info(f"LoggerMiddleware: Request finished for {request.method} {request.url.path} in {process_time:.4f}s. Status: {response.status_code}") return response # 인증을 위한 사용자 정의 미들웨어 클래스 class AuthenticationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): logger.info(f"AuthenticationMiddleware: Checking authentication for {request.url.path}") if request.url.path.startswith("/admin"): auth_header = request.headers.get("Authorization") if not auth_header or auth_header != "Bearer mysecrettoken": logger.warning(f"AuthenticationMiddleware: Unauthorized access to {request.url.path}") return Response("Unauthorized", status_code=status.HTTP_401_UNAUTHORIZED) request.state.user_id = "admin_user_from_token" # state에 데이터 저장 logger.info(f"AuthenticationMiddleware: Authorized access to {request.url.path}") response = await call_next(request) # 제어권 양도 return response app.add_middleware(AuthenticationMiddleware) # 미들웨어 명시적으로 추가 @app.get("/public") async def read_public(): logger.info("PublicEndpoint: Accessing public endpoint") return {"message": "This is a public endpoint"} @app.get("/admin/dashboard") async def read_admin_dashboard(request: Request): user_id = getattr(request.state, "user_id", "anonymous") logger.info(f"AdminDashboardEndpoint: User {user_id} accessing dashboard") return {"message": "Welcome to the admin dashboard", "user": user_id} # 실행 방법: uvicorn main:app --reload
/admin/dashboard
에 대한 실행 흐름:
- 요청 도착.
logger_middleware
:logger.info("LoggerMiddleware: Request started...")
가 실행됩니다.logger_middleware
에서await call_next(request)
호출. 제어권이 다음 미들웨어로 전달됩니다.AuthenticationMiddleware.dispatch
:logger.info("AuthenticationMiddleware: Checking authentication...")
가 실행됩니다.- 승인된 경우:
logger.info("AuthenticationMiddleware: Authorized...")
가 실행됩니다.await call_next(request)
가 호출됩니다. 제어권이 라우트 핸들러로 전달됩니다. read_admin_dashboard
(라우트 핸들러):logger.info("AdminDashboardEndpoint: User ... accessing dashboard")
가 실행됩니다. FastAPI가JSONResponse
로 변환하는 딕셔너리가 반환됩니다.- 라우트 핸들러 반환. 제어권이 스택을 따라 위로 역류합니다.
AuthenticationMiddleware.dispatch
(후처리): 이 예시에는 명시적인 후처리가 없습니다 (await call_next
이후). 그런 다음return response
는 응답을 더 위로 보냅니다.AuthenticationMiddleware.dispatch
반환.logger_middleware
(후처리):logger.info("LoggerMiddleware: Request finished...")
가 실행되고X-Process-Time
헤더가 응답에 추가됩니다.return response
는 응답을 클라이언트로 보냅니다.- 응답이 클라이언트로 전송됩니다.
/admin/dashboard
(승인되지 않은 경우) 실행 흐름:
- 1-4 단계는 동일합니다.
AuthenticationMiddleware.dispatch
:Authorization
헤더가 없음을 감지합니다.logger.warning("AuthenticationMiddleware: Unauthorized access...")
가 실행됩니다.return Response(...)
: 승인되지 않은 응답이 즉시 반환됩니다.await call_next(request)
는 호출되지 않습니다. 라우트 핸들러 및 후속 미들웨어는 건너뜁니다.- 제어권이 즉시
logger_middleware
로 역류합니다. logger_middleware
(후처리):logger.info("LoggerMiddleware: Request finished...")
가 실행되어 인증 미들웨어의 401 응답을 처리합니다.- 응답이 클라이언트로 전송됩니다.
FastAPI의 핵심: Gin과 마찬가지로 미들웨어는 서로를 감쌉니다. await call_next(request)
는 현재 미들웨어를 일시 중지하고, 다운스트림 구성 요소를 실행한 다음, 반환된 response
객체와 함께 실행을 재개합니다. 응답 객체를 조기에 반환하는 것(예: 승인되지 않은 요청의 경우)은 체인을 효과적으로 단축합니다. FastAPI는 request.state
를 사용하여 미들웨어와 라우트 핸들러 간에 데이터를 전달할 수도 있습니다.
결론
Gin 및 FastAPI와 같은 프레임워크에서 미들웨어의 정확한 실행 순서를 이해하는 것은 예측 가능하고 관리하기 쉬우며 안전한 웹 애플리케이션을 구축하는 데 기본입니다. 두 프레임워크 모두 "래퍼" 또는 "양파" 모델을 사용하여 미들웨어가 요청을 사전 처리하고, 제어권을 라우트 핸들러로 스택에서 더 깊이 전달한 다음, 제어권이 다시 올라올 때 응답을 후처리합니다. 제어권 양도(Gin의 c.Next()
, FastAPI의 await call_next(request)
) 및 체인 조기 종료(Gin의 c.AbortWithStatusJSON()
, FastAPI의 조기 return Response()
) 개념은 이 흐름을 숙달하는 데 중요합니다. 이러한 메커니즘을 내면화함으로써 개발자는 로깅, 인증 및 오류 처리와 같은 교차 관심사를 효율적으로 구현하여 강력하고 확장 가능한 백엔드 아키텍처를 보장할 수 있습니다. 미들웨어는 모든 요청 및 응답의 여정을 조정하여 API 상호 작용의 본질을 형성합니다.