FastAPI 어플리케이션이 클라이언트 요청을 처리하는 단계: 요청 처리 파이프라인

ZEDY·2025년 1월 3일
0

개발

목록 보기
11/11

베트남에서 백엔드 개발 인턴을 하고 있다. 나의 업무는 python FastAPI를 만드는 것.
오늘 사수님께서 코드리뷰를 해주셨고, 관련한 레슨런을 적어보려고 한다.

FastAPI 애플리케이션이 클라이언트 요청을 처리하는 단계를 우선 설명해주셨다. 클라이언트의 요청이 들어오면 여러 단계를 거쳐 최종적으로 엔드포인트 함수에 도달하며, 이를 "요청 처리 파이프라인"이라고 한다.


FastAPI 요청 처리 흐름

  1. 클라이언트 요청 (브라우저, Postman, 프론트엔드 등)
  2. 미들웨어: 요청/응답 전처리 및 후처리
  3. 라우팅 (Routing): 해당 API 경로와 메서드에 해당하는 함수로 매핑
  4. 데코레이터 및 종속성 주입 (Dependency Injection): 의존성 주입을 통해 밸리데이션과 추가 작업 수행
  5. 요청 본문 검증 (Validation): 요청 데이터가 요청 모델과 일치하는지 검증
  6. 엔드포인트 함수 실행 (Controller Function): 실제 비즈니스 로직을 처리
  7. 응답 처리 (Response Handling): 클라이언트에 응답 반환

단계별 요청 처리 흐름

1. 미들웨어 (Middleware)

미들웨어는 요청이 들어오거나 응답이 반환될 때 가장 먼저 또는 가장 마지막에 실행된다.

역할:

  • 요청 전후에 공통 작업 수행 (로그 작성, 인증, 요청 시간 측정 등).
  • 미들웨어는 다음 단계로 요청을 넘기거나 중단할 수 있다.

예제 코드:

@app.middleware("http")
async def log_request_time(request: Request, call_next):
    print(f"Incoming request: {request.url}")
    response = await call_next(request)  # 다음 단계로 넘김
    print(f"Completed request: {request.url}")
    return response

나는 OS를 배울 때 이 미들웨어를 들어봐서 이게 어플리케이션을 만들 때에도 코드 상으로 구현을 하는 것이 처음이었다.

fastAPI middleware

이 docs에 의하면, FastAPI 앱에도 미들웨어를 직접 추가할 수 있다고 한다.
특정 업무를 수행하기 전 모든 request, 그리고 response를 주기 전에 수행되는 function이라고 한다.

왜 미들웨어에서 Request를 검증하는가?

  1. 일관된 검증 및 보안 강화
    • 사용자 인증, IP 검증, 토큰 검증 등을 미들웨어에서 수행함으로써 모든 엔드포인트에 보안 검증 로직을 반복하지 않고 한 번에 처리할 수 있다.
  2. 리소스 낭비 방지
  3. 코드 중복 최소화
  4. 로깅 및 모니터링 용이성

그런데 내가 기존에 개발한 파일 업로드 기능에 관해서는 일단 그대로 하고, 나중에 도입을 하는 것을 추천하셨다. 아마 유저 관리 관련해서는 해봐야겠다.


2. 라우터(Router)

  • FastAPI는 요청 경로HTTP 메서드 (GET, POST 등)를 기반으로 적절한 함수로 라우팅한다.
  • 예를 들어 /api/upload-docs/에 대한 POST 요청은 upload_docs 함수로 전달된다.
@app.post("/api/upload-docs/")
async def upload_docs(file: UploadFile):
    ...

3. 의존성 주입 (Dependency Injection)

  • FastAPI는 함수 호출 시 필요한 의존성을 자동으로 주입한다.
  • 예를 들어, Request, Depends, File 등은 FastAPI가 필요한 객체를 주입한다.
from fastapi import Depends, Request

async def validate_user(request: Request):
    # 요청 헤더에 토큰이 있는지 검증
    token = request.headers.get("Authorization")
    if not token:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return token

@app.post("/api/upload-docs/")
async def upload_docs(file: UploadFile, token=Depends(validate_user)):
    ...

위 코드에서 Depends(validate_user)upload_docs 함수가 호출될 때 사용자 검증 함수를 먼저 실행한다.


4. 요청 본문 검증 (Validation)

FastAPI는 pydantic을 사용해 요청 본문을 자동으로 밸리데이션한다.

  • 요청 데이터가 잘못되었을 경우, 자동으로 400 Bad Request 응답을 반환한다.

예제 (Pydantic 모델 사용):

from pydantic import BaseModel

class FileUploadRequest(BaseModel):
    filename: str

@app.post("/api/validate-upload/")
async def validate_upload(file_data: FileUploadRequest):
    return {"message": f"Received file: {file_data.filename}"}
  • 요청 데이터가 {"filename": "sample.docx"} 형태여야 한다.
  • 만약 filename 필드가 없으면 400 응답이 반환된다.

5. 컨트롤러 (엔드포인트 함수)

  • 컨트롤러 함수는 비즈니스 로직을 수행한다.
  • 컨트롤러 함수 내부에서는 서비스 레이어를 호출해 파일을 저장하거나 데이터를 반환한다.
@app.post("/api/upload-docs/")
async def upload_docs(file: UploadFile = File(...)):
    file_path = save_file(file.filename, file.file)  # 서비스 레이어 호출
    return {"message": "File uploaded", "file_path": file_path}

6. 응답 처리 (Response Handling)

  • 정상 응답: 컨트롤러 함수가 반환한 데이터를 클라이언트에 응답한다.
  • 에러 응답: 밸리데이션 오류, 인증 실패, 서버 오류 등은 해당 HTTP 상태 코드와 에러 메시지를 반환한다.

예를 들어:

{
  "detail": "File not found"
}

요약된 요청 처리 흐름

  1. 미들웨어: 요청을 전처리(로깅, 인증 등)하거나 응답을 후처리.
  2. 라우팅: 요청을 해당 엔드포인트로 매핑.
  3. 의존성 주입: 필요하면 Depends()로 필요한 검증 또는 공통 작업 수행.
  4. 밸리데이션: 요청 데이터가 올바른지 확인 (pydantic 모델).
  5. 컨트롤러 함수: 비즈니스 로직 수행 및 응답 반환.
  6. 응답 처리: 클라이언트에 응답 반환.

결론

사수님께서 강조하신 내용은 미들웨어 → 서비스 레이어 → 컨트롤러 함수 흐름으로 코드의 유지보수성과 확장성을 높이는 구조를 고려하라는 의미다. 파일 업로드 API처럼 간단한 경우에는 이런 단계를 모두 적용하지 않아도 괜찮지만, 복잡한 기능을 추가할 때는 이러한 구조를 통해 코드 가독성과 재사용성을 높일 수 있다.

profile
Spring Boot 백엔드 주니어 개발자

0개의 댓글