Spring Boot 서버에서 여러 장의 이미지 리스트(files)와 텍스트 옵션(scam_type, image_type)을 전송했습니다.
단일 JSON 데이터가 아닌 바이너리 파일과 텍스트가 혼합된 복합 데이터를 파이썬(FastAPI) 서버에서 어떻게 안전하게 파싱하고 수신하는지, 그 과정을 단계별로 알아보겠습니다.
Spring Boot에서 List<MultipartFile> 형태로 전송했으므로, FastAPI에서도 이를 수용할 수 있는 리스트 타입의 파라미터를 선언해야 합니다.
기존 단일 파일 수신 시 서버 메모리를 보호해 주던 UploadFile 타입을 List로 감싸 다중 파일 배열로 확장합니다. UploadFile이 내부적으로 Spooling 처리를 해주기 때문에, 수십 장의 이미지 트래픽이 한 번에 유입되더라도 서버 다운 위험 없이 안전하게 받아낼 수 있습니다.
from typing import List
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/analyze")
async def analyze_images(
# Java 측의 Key 이름("files")과 정확히 일치시켜야 합니다.
files: List[UploadFile] = File(...)
):
단일 파일만 받을 때와 달리, 클라이언트(Spring Boot)가 파일과 함께 텍스트 옵션(scam_type, image_type)을 추가로 전송했습니다. 이때 이 텍스트 파라미터들을 FastAPI에서 수신하려면 단순한 타입 선언(str)만으로는 부족하며, 반드시 = Form(...)을 명시해야 합니다.
그 기술적인 이유는 FastAPI의 기본 데이터 파싱 전략 때문입니다.
FastAPI는 POST 요청의 body를 읽을 때 기본적으로 application/json 규격을 기대합니다. 만약 파라미터를 scam_type: str이라고만 선언하면, FastAPI는 이를 JSON 바디에서 찾으려 하거나 URL의 쿼리 파라미터로 취급해 버립니다.
하지만 현재 클라이언트의 요청은 파일이 섞인 multipart/form-data 규격이며, 이 데이터들은 고유한 boundary로 나뉘어 전송됩니다. 따라서 FastAPI의 의존성 주입 시스템에게 "이 텍스트는 JSON이나 URL 파라미터가 아니라, multipart 바디 내부의 Form 데이터 영역에서 파싱해야 한다"라고 정확한 파싱 위치를 강제하기 위해 = Form(...) 선언이 필수적입니다. 이를 누락할 경우 데이터를 찾지 못하고 422 Unprocessable Entity 에러를 반환하게 됩니다.
from fastapi import FastAPI, File, UploadFile, Form
@app.post("/analyze")
async def analyze_images(
files: List[UploadFile] = File(...),
scam_type: str = Form(...),
image_type: str = Form(...)
):
수신한 파일 리스트를 반복문으로 돌면서, AI 분석을 위해 실제 파일 데이터를 메모리에 바이트(Byte) 형태로 읽어 들입니다.
@app.post("/analyze")
async def analyze_images(
files: List[UploadFile] = File(...),
scam_type: str = Form(...),
image_type: str = Form(...)
):
for file in files:
# 파일을 비동기적으로 읽어 바이트 스트림 반환
content = await file.read()
모든 분석이 끝난 후, Spring Boot 서버가 기다리고 있는 FraudResponseDTO 구조에 맞춰 JSON 형태로 결과를 반환합니다.
from typing import List
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/analyze")
async def analyze_images(
files: List[UploadFile] = File(...),
scam_type: str = Form(...),
image_type: str = Form(...)
):
for file in files:
content = await file.read()
# AI 모델 분석 로직이 들어갈 자리
# 분석 결과를 Java 서버의 DTO 필드명에 맞춰 반환
return {
"status": "FRAUD", # ENUM 타입에 맞춘 대문자 고정
"fraudScore": 85.5, # 산출된 위험도 점수
"description": f"총 {len(files)}장의 {image_type} 데이터가 {scam_type} 유형으로 분석 완료되었습니다."
}