
API ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ํ์ผ๊ณผ DTO(JSON)๋ฅผ ํจ๊ป ๋ฐ์์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
์ง๊ธ ๋ด๊ฐ ๊ฐ๋ฐํ๊ณ ์๋ ํ๋ซํผ์์๋, ํ๋ก์ ํธ ์
๋ก๋ ๊ธฐ๋ฅ์ด ์กด์ฌํ๋ค. ํ๋ก์ ํธ ์ธ๋ถ ์ ๋ณด(JSON)์ ์ธ๋ค์ผ ํ์ผ์ ๋์์ ์
๋ก๋ํด์ผ ํ๋ค.
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<SuccessResponse<Void>> uploadProject(
@Parameter(hidden = true)
@CurrentUserId
Long userId,
@RequestPart(value = "thumbnailFile", required = false)
MultipartFile thumbnailFile,
@RequestPart @Validated
UploadProjectWebRequest webRequest
);
์ฌ๊ธฐ์ thumbnailFile์ ์ ํ ํ์ผ์ด๊ณ , webRequest๋ JSON DTO๋ค.
๋ฌธ์ ๋ Swagger, Postman, ํ๋ก ํธ์๋ ์ฝ๋์์ webRequest ํํธ์
Content-Type์ด application/json์ด ์๋, application/octet-stream์ด๋
text/plain์ผ๋ก ๋ค์ด์ค๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค๋ ๊ฒ์ด๋ค. ์ด๋ Spring์ Jackson์
์ด์ฉํ ์ญ์ง๋ ฌํ๋ฅผ ์๋ํ์ง ๋ชปํ๊ณ ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๋๋, ์ฒ์์ ์ค์จ๊ฑฐ, ํฌ์คํธ๋งจ ๋ฑ์์ ํ
์คํธ๋ฅผ ํ ๋ ํ์ฌ ์กฐ๊ฑด ๋ด์์ ํด๊ฒฐํด๋ณด๋ ค๊ณ ๊ณ์ ๋
ธ๋ ฅํด๋ณด์์ผ๋ ๊ณ์ํ์ฌ ์คํจํ์๋ค.
Content type 'application/octet-stream' not supported
Spring MVC๋ multipart ์์ฒญ์ ์ฒ๋ฆฌํ ๋ ๋ค์ ํ๋ฆ์ ๋ฐ๋ฅธ๋ค.
Client (multipart/form-data)
โ DispatcherServlet
โ HandlerAdapter
โ @RequestPart(webRequest) ๋ฐ์ธ๋ฉ
โ HttpMessageConverter ์ ํ
์ด๋ Converter ์ ํ ๊ธฐ์ค์ Content-Type + ํ๋ผ๋ฏธํฐ ํ์ ์ด๋ค.\
application/json โ MappingJackson2HttpMessageConverterapplication/octet-stream โ Jackson ํ๋ณด์์ ์ ์ธ โ์ฆ, JSON์ด octet-stream์ผ๋ก ๋ค์ด์ค๋ฉด Jackson์ด ๊ฑด๋๋ฆฌ์ง ๋ชปํ๋ค๋ ๊ฒ
ํต์ฌ ๋ฌธ์ ๋ค.
์ด์์ ์ธ ํด๊ฒฐ์ ํ๋ก ํธ/ํ
์คํธ ๋๊ตฌ๊ฐ ๋ฐ๋์ application/json์ผ๋ก
๋ณด๋ด๋๋ก ๊ฐ์ ํ๋ ๊ฒ์ด๋ค.
ํ์ง๋ง ํ์ค์ ์ผ๋ก๋ ์ค์๊ฐ ๋ฐ๋ณต๋๋ค. ๋ฐ๋ผ์ ์๋ฒ์์ ๋ฐฉ์ด์ ์ผ๋ก
์ฒ๋ฆฌํด์ฃผ๋ ๊ฒ์ด ๋ ์ค๋ฌด์ ์ด๋ค.
๐ ์ด์ ๋ํ ํด๊ฒฐ๋ฐฉ๋ฒ์ ๊ฐ๋จํจ
**Jackson ์ปจ๋ฒํฐ๊ฐ application/octet-stream์ด๋๋ผ๋ ์ฝ๊ธฐ(read)๋ Jackson์ผ๋ก ์๋ํ๊ฒ ๋ง๋ ๋ค.
๊ทธ๋์ webRequest ํํธ๊ฐ octet-stream์ผ๋ก ๋ค์ด์๋ ๊ฐ์ ๋ก JSON ํ์ฑ์ ์๋ํด์ DTO๋ก ๋ฐ์ธ๋ฉํ ์ ์๋ค.
๋จ, ์๋ต(์ฐ๊ธฐ)์๋ ๊ด์ฌํ์ง ์๋๋ก canWrite๋ ๋ชจ๋ false ์ฒ๋ฆฌํ๋ค.
@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
@Override protected boolean canWrite(MediaType mediaType) { return false; }
@Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return false; }
@Override public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) { return false; }
}
@Configuration
class WebConfig implements WebMvcConfigurer {
private final MultipartJackson2HttpMessageConverter octetReader;
WebConfig(MultipartJackson2HttpMessageConverter octetReader) {
this.octetReader = octetReader;
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// ์ฝ๊ธฐ ์ ์ฉ octet-stream Jackson ์ปจ๋ฒํฐ๋ฅผ ์ต์๋จ์ ์ถ๊ฐ
converters.add(0, octetReader);
}
}
์ด์ webRequest ํํธ๊ฐ ์ข
์ข
application/octet-stream์ผ๋ก ๋ค์ด์๋
Jackson์ด ์ฝ์ด์ DTO ๋ณํ์ ์ํํ ์ ์๋ค.
curl -X POST http://localhost:8080/projects/upload -H "Content-Type: multipart/form-data" -F "thumbnailFile=@/path/to/image.png" -F 'webRequest={
"title":"ํ๋ก์ ํธ๋ช
",
"topicId":2,
"analysisPurposeId":2,
"dataSourceId":3,
"authorLevelId":3,
"isContinue":true,
"parentProjectId":3,
"content":"์ง๊ธ ๋ฐ์ดํฐ ์ถ์ฒ์ ๋ํด์ ~~.",
"dataIds":[1,3]
};type=application/json'
curl -X POST http://localhost:8080/projects/upload -H "Content-Type: multipart/form-data" -F "thumbnailFile=@/path/to/image.png" -F 'webRequest={
"title":"ํ๋ก์ ํธ๋ช
",
"topicId":2,
"analysisPurposeId":2,
"dataSourceId":3,
"authorLevelId":3,
"isContinue":true,
"parentProjectId":3,
"content":"์ง๊ธ ๋ฐ์ดํฐ ์ถ์ฒ์ ๋ํด์ ~~.",
"dataIds":[1,3]
}'
์ฌ๊ธฐ WebRequest์ ์ฐ์ธ UploadProjectWebRequest๋ ๋จ์ํ JSON ์ปจํ
์ด๋๊ฐ ์๋๋ผ, ์
๋ ฅ ๊ท์น์
๋ช
ํํ ์ก์์ฃผ๋ ๊ณ์ฝ์ด๋ค.
@Schema(description = "ํ๋ก์ ํธ ์
๋ก๋ ์น ์์ฒญ DTO")
public record UploadProjectWebRequest(
@NotBlank(message = "์ ๋ชฉ์ ์
๋ ฅํด์ฃผ์ธ์")
String title,
@NotNull @Min(1)
Long topicId,
@NotNull @Min(1)
Long analysisPurposeId,
@NotNull @Min(1)
Long dataSourceId,
@NotNull @Min(1)
Long authorLevelId,
@NotNull
Boolean isContinue,
@Min(1)
Long parentProjectId,
@NotBlank(message = "๋ด์ฉ์ ์
๋ ฅํด์ฃผ์ธ์")
String content,
@NotNull
List<Long> dataIds
) {}

application/json์ด ์๋๋ฉด Jackson ์ปจ๋ฒํฐ๊ฐ ์๋ํ์ง ์๋๋ค.\MultipartJackson2HttpMessageConverter๋ฅผapplication/octet-stream๋ JSON์ผ๋ก ํ์ฑ๋๋๋ก ํ๋ค.\๐ ์ด๋ ๊ฒ ์ค์ ํด๋๋ฉด Swagger/Postman/ํ๋ก ํธ ์ค์๊น์ง ๋ฐฉ์ดํ ์ ์๊ณ ,
API ์ฌ์ฉ์ฑ์ด ํฌ๊ฒ ๊ฐ์ ๋๋ค. ๐