미들웨어 적용도 해볼 겸 API response 구조를 통일해보려 했다.
문서를 보니, 미들웨어 적용 자체는 꽤 간단했다. ( response 내부 까보기 전까진..ㅎ )
https://fastapi.tiangolo.com/tutorial/middleware/#create-a-middleware
Middleware - FastAPI
FastAPI framework, high performance, easy to learn, fast to code, ready for production
fastapi.tiangolo.com
pydantic으로 BaseResponse 클래스 만들고 적용
base_response.py
class BaseResponse(BaseModel):
"""공통 response DTO"""
status_code: int
data: Any
message: str = "success"
main.py
@app.middleware("http")
async def apply_base_response(request: Request, call_next):
response = await call_next(request)
return BaseResponse(
status_code=response.status_code,
data=response,
)
TypeError: 'BaseResponse' object is not callable 오류
해당 오류가 발생하는 이유는 FastAPI가 기대하는 Response 객체가 아니기 때문이다.
그렇다면, FastAPI가 기대하는 Response 객체의 형태는 무엇일까?
Starlette의 Response 구조를 따르는 FastAPI
FastAPI는 기본적으로 starlette의 response구조를 따른다.
@app.middleware("http")
async def apply_base_response(request: Request, call_next):
response = await call_next(request)
status_code = response.status_code
body = response.body
return JSONResponse(
content=BaseResponse(status_code=status_code, data=body).model_dump(),
status_code=status_code,
)
starlette의 response구조대로 맞춰서 수정해보았다.
AttributeError: '_StreamingResponse' object has no attribute 'body' 오류
위와 같이 body를 참조해서 가져올수가 없는 형태이다.
왜 그럴까?
StreamingResponse
starlette의 response 구조다.
class StreamingResponse(Response):
def __init__(self, content: AsyncIterable[bytes], status_code: int = 200):
self.body_iterator = content
self.status_code = status_code
보면 body를 chunk단위로 쪼개서 전달하고 있다.
그래서 .body가 아닌 body_iterator를 통해서 response 전문에 해당하는 내용은 iterable한 데이터를 content필드에 넘겨줘야 한다.
Request 객체 내 body 까보기
다시 FastApi로 돌아와서 Request객체 내 body를 까보았다.
어떤식으로 content를 넘겨야 할지 파악하기 위해서다.
async def body(self) -> bytes:
if not hasattr(self, "_body"):
chunks: list[bytes] = []
async for chunk in self.stream():
chunks.append(chunk)
self._body = b"".join(chunks)
return self._body
- body값은 비동기 스트리밍 방식으로 읽고, _body에 캐싱을 한다.
- _body값에서는 stream()을 통해 요청 본문을 읽은 chunk들을 합쳐서 저장된다.
따라서 아래와 같이 코드를 수정하고, 미들웨어를 활용하여 API response를 통일된 커스텀 구조로 처리하니 정상적으로 동작하는 것을 확인할 수 있었다.
@app.middleware("http")
async def apply_base_response(request: Request, call_next):
response = await call_next(request)
status_code = response.status_code
body = b""
async for chunk in response.body_iterator:
body += chunk
try:
body = json.loads(body)
except json.JSONDecodeError:
body = body.decode()
return JSONResponse(
content=BaseResponse(status_code=status_code, data=body).model_dump(),
status_code=status_code,
)
'프로그래밍 > Python' 카테고리의 다른 글
Pydantic BaseSettings로 환경변수 관리하기 – @property 패턴 적용기 (0) | 2025.06.13 |
---|---|
[ FastAPI ] HTTPException에서 CustomException으로 예외 처리 개선 (2) | 2025.03.20 |
black으로 코드스타일 포매팅 (0) | 2025.02.11 |
언더스코어 인 파이썬 ( 파이썬 객체지향적으로 사용하기 ) (1) | 2025.01.30 |
Pydantic.BaseSetting을 사용한 환경변수 관리 (0) | 2025.01.26 |