우와한 개발자

[Spring] WebFlux로 FastAPI 프록시 구현하기 — MultipartBodyBuilder, FilePart, WebClient

by 우와한개발자

1. 개요

  • Spring Boot가 클라이언트 요청을 받아 FastAPI 서버로 중계하는 프록시 구조다
  • 파일과 텍스트를 함께 전달해야 하기 때문에 멀티파트 요청을 구성해서 전송한다
  • 클라이언트가 FastAPI 서버에 직접 접근하지 않고 Spring Boot를 통해서만 통신하게 해서 보안, 인증, 라우팅을 중앙에서 관리할 수 있다
클라이언트 → Spring Boot (프록시) → FastAPI
           인증/라우팅 담당        AI 처리 담당

 

2. 멀티파트 (Multipart)

  • 파일과 텍스트를 함께 전송할 때 사용하는 HTTP 전송 방식
Content-Type: multipart/form-data

------boundary
Content-Disposition: form-data; name="message"
안녕하세요

------boundary
Content-Disposition: form-data; name="file"; filename="photo.png"
Content-Type: image/png

(바이너리 데이터...)
------boundary--
항목 설명
MultipartBodyBuilder 멀티파트 바디를 조립하는 빌더
.part() 텍스트 파트 추가
.asyncPart() 파일을 스트림으로 추가 (메모리 효율적)
.filename() 서버가 파일명을 알 수 있도록 지정
.contentType() 파일 형식 지정 (image/png 등)

 

2. 구현

1) ObjectDetectionProxyController

  • 클라이언트 요청을 받아 FastAPI 서버로 중계하는 프록시
클라이언트
  ↓  POST /api/detect/proxy
  ↓  (message + file + responseType)
SpringBoot (프록시)
  ↓  POST /detect
  ↓  멀티파트 재조립해서 전달
FastAPI (객체 감지)
  ↓  결과 반환
클라이언트
@RestController
@RequiredArgsConstructor
public class ObjectDetectionProxyController {

    private final WebClient webClient;

    @PostMapping("/api/detect/proxy")
    public Mono<String> service(
            @RequestPart("message") String message,       // 텍스트
            @RequestPart("file") FilePart file,           // 파일 (WebFlux 전용)
            @RequestPart("responseType") String responseType) {

        // 멀티파트 바디 조립
        MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
        bodyBuilder.part("message", message);
        bodyBuilder.part("responseType", responseType);
        bodyBuilder.asyncPart("file", file.content(), DataBuffer.class)
                .filename(file.filename())
                .contentType(
                    file.headers().getContentType() != null
                    ? file.headers().getContentType()     // 원본 Content-Type 유지
                    : MediaType.APPLICATION_OCTET_STREAM  // 없으면 기본값
                );

        // FastAPI 서버로 전송
        return webClient.post()
                .uri("/detect")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(BodyInserters.fromMultipartData(bodyBuilder.build()))
                .retrieve()
                .bodyToMono(String.class)
                .timeout(Duration.ofSeconds(30));  // 30초 초과 시 에러
    }
}

 

2) build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    testCompileOnly 'org.projectlombok:lombok'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    testAnnotationProcessor 'org.projectlombok:lombok'
}
  • spring-boot-starter-webflux 만 사용
  • webmvc 관련 의존성 혼용 금지 → MVC가 활성화되면 FilePart 바인딩 오류가 발생한다.

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기