Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultipartFile 요청 명세 수정 완료 #453

Closed
wants to merge 1 commit into from
Closed

Conversation

limehee
Copy link
Collaborator

@limehee limehee commented Aug 7, 2024

Summary

#452

현재 MultipartFile의 경우 @RequestParam으로 데이터를 전달받고 있으나, 이는 잘못된 방식입니다. @RequestBody를 사용하여 데이터를 올바르게 전달받을 수 있도록 수정합니다.

Tasks

  • @RequestParam 어노테이션을 @RequestBody로 변경

@limehee limehee added the 🔨 Refactor 코드 수정 및 개선 label Aug 7, 2024
@limehee limehee self-assigned this Aug 7, 2024
@limehee limehee linked an issue Aug 7, 2024 that may be closed by this pull request
1 task
@SongJaeHoonn
Copy link
Contributor

SongJaeHoonn commented Aug 7, 2024

@RequestParam annotation can also be used to associate the part of a "multipart/form-data" request with a method argument supporting the same method argument types. The main difference is that when the method argument is not a String or raw MultipartFile / Part, @RequestParam relies on type conversion via a registered Converter or PropertyEditor while RequestPart relies on HttpMessageConverters taking into consideration the 'Content-Type' header of the request part. RequestParam is likely to be used with name-value form fields while RequestPart is likely to be used with parts containing more complex content e.g. JSON, XML

@RequestParam도 multipart/form-data를 처리하는 데 사용할 수 있다고 합니다.
MultipartFile을 요청받을 때, 저는 단일로 파일을 받게 될 때에는 @RequestParam을 사용하고,
다른 JSON 양식과 함께 multipartfile을 받을 때에는 @RequestPart를 사용합니다.

@RequestParam으로 multipartfile을 처리하는 것이 잘못된 방식이라는 말은 들어본 적이 없는데, 어떤 부분에서 잘못된 것인가요?

Copy link
Collaborator

@mingmingmon mingmingmon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestPart.html

아마 @SongJaeHoonn 님께서도 해당 docs를 살펴보신 것 같습니다. 저도 MultipartFile을 전달할 때 @RequestPart@RequestParam 사이에서 고민한 적은 있어도 @RequestBody를 사용할 경우 장점을 잘 모르겠습니다.

오히려 아래와 같은 상황이 우려됩니다.

복잡성 증가
파일 데이터를 JSON 형식으로 인코딩해야 하므로 클라이언트와 서버 모두에서 추가적인 인코딩 및 디코딩 작업이 필요해보입니다.
성능 저하
파일 데이터를 Base64 등으로 인코딩하면 데이터 크기가 커져 네트워크 대역폭과 메모리 사용량이 증가할 것으로 예상됩니다.
파일 크기 제한
JSON으로 인코딩된 파일은 크기가 커질 수 있고, 경우에 따라 HTTP 요청의 크기 제한 때문에 문제가 생길 수 있을 것 같습니다.

@RequestBody의 사용 필요성을 조금 더 설명해주시면 이해하는데 더 도움이 될 것 같습니다!

@gwansikk gwansikk self-requested a review August 7, 2024 23:55
@gwansikk gwansikk marked this pull request as draft August 7, 2024 23:55
@limehee
Copy link
Collaborator Author

limehee commented Aug 8, 2024

처음 작업하게 된 계기는 Swagger 2.5.0 이상의 버전에서 MultipartFile을 @RequestParam으로 받을 때 타입 추론 오류가 발생했기 때문입니다.

해당 라이브러리 메인테이너는 @RequestBody를 사용하면 된다고 답변했으며, 실제로 이렇게 변경했을 때 정상 작동하는 것을 확인했습니다.

또한 작업을 진행하기 전에 프론트 팀에게 문의한 결과, 이미 데이터를 Body에 담아 전송하고 있다는 답변을 받았고, 이에 따라 @RequestParam을 사용하는 것은 바람직하지 않다는 결론을 내렸습니다.

@SongJaeHoonn 님과 @mingmingmon 님께서 언급하신 부분 또한 찾아보았습니다. 공식 문서와 다양한 자료를 참고해봤을 때, 일반적으로 @RequestParam을 이용하여 MultipartFile을 처리하고, DTO를 같이 전달받을 필요가 있을 때 @RequestPart를 이용하는 게 맞는 것 같습니다.

자세히 알아보지 않고 성급히 작업한 점에 대해 죄송하게 생각합니다.

@gwansikk
Copy link
Member

gwansikk commented Aug 8, 2024

@limehee @SongJaeHoonn @mingmingmon

해당 이슈와 관련하여 논의가 필요할 거 같아 의견을 남겨요. 프론트엔드팀에서는 API 문서와 테스트 도구를 Swagger를 사용하고 있어요. 댓글 살펴봤을 때 @RequestParam로 설정 시 Swagger에서 파일 업로드를 테스트할 수 없다고 확인이 되는데, 다른 대안이 있을까요? (물론 Postman과 같이 서드파티 도구를 사용하여 테스트를 할 수 있겠지만, 프론트엔드팀에서는 사용도구로는 Swagger를 메인으로 사용하고 있어요.)

@gwansikk gwansikk requested a review from mingmingmon August 8, 2024 01:17
@limehee
Copy link
Collaborator Author

limehee commented Aug 9, 2024

@limehee @SongJaeHoonn @mingmingmon

해당 이슈와 관련하여 논의가 필요할 거 같아 의견을 남겨요. 프론트엔드팀에서는 API 문서와 테스트 도구를 Swagger를 사용하고 있어요. 댓글 살펴봤을 때 @RequestParam로 설정 시 Swagger에서 파일 업로드를 테스트할 수 없다고 확인이 되는데, 다른 대안이 있을까요? (물론 Postman과 같이 서드파티 도구를 사용하여 테스트를 할 수 있겠지만, 프론트엔드팀에서는 사용도구로는 Swagger를 메인으로 사용하고 있어요.)

팀원들의 의견도 들어봐야겠지만, 해당 이슈와 관련하여 메인테이너에게 문의를 남겨둔 상태입니다. 상황을 좀 더 지켜봐야 할 것 같습니다.

Copy link
Collaborator

@mingmingmon mingmingmon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swagger 버전 차이로 인한 문제였군요.
@RequestParam을 사용할 경우 Swagger를 통한 파일 업로드 테스트가 불가능하다면, @RequestBody를 사용해야 될 것 같습니다.

다만 제가 List<MultipartFile>@RequestPart , 단일 MultipartFile@RequestPart을 조합해서 테스트 해보니, 아래와 같은 결과가 나왔습니다. 결론적으로 List<MultipartFile>의 전달을 @RequestPart로 하는 것을 제안합니다.

실행환경

org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0
'org.springframework.boot' version '3.3.2'

실행결과

  • List<MultipartFile>@RequestPart 인 경우 -> @RequestBodyList<MultipartFile>이 전달되면서 정상 작동
image
  • 단일 MultipartFile@RequestParam인 경우 -> @RequestBodyMultipartFile이 전달되면서 정상작동
image

이전에 저와 @SongJaeHoonn 님이 주장했던 것에 따르면 MultipartFile@RequestParam@RequestPart로 전달이 가능한 것이고, @RequestBody로 전달하는 것 보다 표준화 되고 추천되는 방법이라고 했습니다.

해당 이슈를 해결하기 위해서 @RequestParam, @RequestPart, @ReqeustBody의 차이점을 더 알아본 결과 아래와 같이 정리 할 수 있었습니다.
(참고 자료 : https://velog.io/@hwan2da/Spring-RequestBody-vs-RequestParam-vs-RequestPart-vs-ModelAttribute, https://somuchthings.tistory.com/160)

@RequestBody

  • XML과 JSON을 전달하는데 사용됩니다. 코드 상에서 consumes = MediaType.MULTIPART_FORM_DATA_VALUE를 명시하지 않으면 @RequestBody로는 원래 MultipartFile을 전달할 수 없습니다. 아래에 실행 사진 첨부합니다.
image

@RequestParam

  • 메소드 파라미터 타입이 String 또는 MultipartFile/Part가 아닌 경우 Converter 또는 PropertyEditor를 참조해 변환을 시도하는 점이 @RequestPart와 차이가 있습니다. MultipartFile의 경우는 MultipartResolver를 통해 변환이 된다고 합니다.

예상하건데, 따라서 Swagger 상에서 단일 MultipartFile@RequestParam으로 전달이 가능하게 된 것은 단일 MultipartFileConverterPropertyEditor를 참조하지 않고 정상적으로 MultipartResolver를 통한 변환이 이루어진 것으로 보입니다.

다만 Swagger측의 버그인지, List<MultipartFile>을 전달할 경우는 오류가 나는 것을 보니, MultipartResolver를 통한 변환이 안 되는 것 같습니다.

@RequestPart

  • 메소드 파라미터 타입이 String 또는 MultipartFile/Part가 아닌 경우 요청 헤더의 Content-Type을 참조하여 해당하는 HttpMessageConveter로 변환을 시도합니다. 마찬가지로 MultipartFile의 경우는 MultipartResolver를 통해 변환을 합니다.
  • 또한 @RequestPartMultipartFile 요청의 특정 파트만을 추출하여 처리합니다. 이 파트는 request body의 일부로 들어오지만, MultipartFile 구조에서 구분된 여러 파트 중 하나를 지정할 수 있습니다. 이 특징 때문에 @RequestPart를 사용했을 때 Swagger에서 @RequestBody로 파일이 전달됨을 확인할 수 있었습니다.

예상하건데, Swagger상에서 List<MultipartFile>@RequestPart로는 잘 변환하는 것을 보면 @RequestParam은 단순히 폼 필드나 쿼리 파라미터, 혹은 파일 데이터를 처리할 때 주로 사용하는 것이고, @RequestPart는 multipart/form-data의 특정 파트를 처리하며, 파일과 함께 JSON 또는 XML 데이터 등 복합 데이터 구조를 다룰 때 사용되는 특징에 의해서 Swagger에서 MultipartFile의 전달을 @RequestPart로 유도하고자 한 것이 아닌가 싶습니다.

마무리

@RequestBodyconsumes = MediaType.MULTIPART_FORM_DATA_VALUE 명시하였을 때 List<MultipartFile>이 전달이 되는 것과 @RequestPartconsumes = MediaType.MULTIPART_FORM_DATA_VALUE를 명시하고 List<MultipartFile>이 전달이 되는 것은 같은 결과가 나타나지만,

@RequestPartMultipartFile을 JSON이나 XML로 표현된 requestBody의 요소들과 함께 전달하여도 MultipartFile 요청의 특정 파트만을 추출하여 처리하는 점과 request body의 일부로 들어오지만, MultipartFile 구조에서 구분된 여러 파트 중 하나를 지정하는 특징 때문에

@RequestBody보다 @RequetPart를 사용하는 것이 더 알맞을 것 같습니다.

@SongJaeHoonn
Copy link
Contributor

@limehee 께서 메인테이너에게 남긴 문의를 통해 해당 이슈가 고쳐질지 기다려 본 후, 만약 해결하기 어렵다면 @mingmingmon 께서 답변주신 @RequestPart 를 사용하는 것이 @RequestBody에 multipartfile을 담는 것보다 더 정형화되고 나은 방법이라 생각합니다!

@gwansikk
Copy link
Member

gwansikk commented Aug 9, 2024

@mingmingmon 민주님 분석 멋집니다..! 해당 내용을 코어팀 블로그에 글 작성해 보는 것은 어떨까요
다른 개발자분들도 겪거나 고민한 문제라고 생각합니다. 해당 인사이트가 잘 공유되었으면 좋겠네요

@gwansikk
Copy link
Member

gwansikk commented Aug 9, 2024

@limehee 해당 PR은 일단 닫고 후에, Swagger 측에서 해당 이슈가 마무리 되면 다시 부탁드립니다~

@gwansikk gwansikk closed this Aug 9, 2024
@limehee limehee deleted the refactor/#452 branch December 23, 2024 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 Refactor 코드 수정 및 개선
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MultipartFile 요청 명세 수정
4 participants