[springboot] org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
예전에 처리한 내용이지만, 이번에 다시 한번 정리할 기회가 생겨서 내용을 정리함.
multipart/formdata 파일업로드 api에서 발생하는 에러에 대한 처리 내용.
[org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]
일반적으로 content-type이 multipart/form-data일때는 프론트에서 formdata로 데이터와 파일을 담아서 보내야 하는데,
multipart/form-data의 내용을 application/json형태로 요청하면 발생한다. (json구조체와 multipart file을 spring controller에서 직접 DTO로 받을려고 하면 발생한다.)
==> json형태의 파라미터를 DTO로 역직렬화를 하지 못하기때문에 발생함.
최근에는 요청을 form을 사용하지 않고, content-type을 application/json으로 하고 복잡한 구조체를 request body에 담아서 보내는 경우가 많다보니 종종 마주칠수 있다.
정상적으로 받을려면 아래의 2가지중에 선택해서 받아야 한다. content-type을 multipart/form-data로 셋팅하고
1) MultipartHttpServeltRequest를 이용
* MultiPartHttpServeletRequest를 직접 이용해서 파리미터와 파일을 받음.
@RequestMapping(value = "/user/{userNo}", method = RequestMethod.POST)
public ResponseEntity<ResultDto> updateUserInfo(MultipartHttpServletRequest mRequest,
@ApiParam(value = "userNo", required = true) @PathVariable(required = true) Long userNo) {
String userName = mRequest.getParameter("name");
String email = mRequest.getParameter("email");
String birthDate = mRequest.getParameter("birthDate");
Map<String, MultipartFile> fileMap = mRequest.getFileMap();
2) @ModelAttribute를 이용
* @ModelAttribute를 이용해서 multipart/formdata의 request내용을 DTO에 셋팅.
@PostMapping("/user/{userNo}")
public ResponseEntity<ResultDto> updateUserInfo(HttpServletRequest request,
@ModelAttribute @Valid UpdateUserDTO updateDTO
) {
List<MultipartFile> files = updateDTO.getFiles();
@Data
public class UpdateUserDTO {
@NotBlank
private String name;
@NotBlank
private String email;
@NotNull
private Date birthDate;
private List<MultipartFile> files;
}
이와같이 처리하면, 복잡한 구조의 request data를 받을수 없는 문제가 있다. list와 object등이 포함된 Json구조체 형태의 데이터는 받을수가 없다. 단순한 파라미터만 받을수 있음. 꼼수로 String jsonStr이라고 파라미터를 받고, json 문자열을 통째로 받는 경우도 있었음.
formdata로 json구조체 파라미터와 file을 보내고, controller에서 DTO로 json구조체를 직접 받는 방법.
@PostMapping(value = "/user/{userNo}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ResultDto> updateUserInfo(@PathVariable Long userNo
, @RequestPart List<MultipartFile> file
, @RequestPart UpdateUserDTO updateDto) {
@Data
public class UpdateUserDTO {
@NotBlank
private String name;
@NotBlank
private String email;
@NotNull
private Date birthDate;
@NotNull
private AddressDTO address;
private List<DepartmentDTO> departmentList;
}
다음의 2가지 방식중에 선택해서 사용가능.
1. request content-type을 따로 지정.
curl --location 'http://localhost:8013/console/game/udateGameDetail' \
--form 'updateParam="{
\"name\": \"eggrok\",
\"email\": \"eggrok@test.com\",
\"address\": {\"country\" : \"korea\", \"state\" : \"seoul\", \"detail\" : \"\"},
\"departmentList\": [
]
}";type=application/json' \
--form 'files=@"/Users/eggrok/Downloads/player_img/gallery/thum-gallery-01.jpeg"' \
--form 'files=@"/Users/eggrok/Downloads/player_img/gallery/thum-gallery-02.jpeg"'
2. HttpMessageConverter를 재정의해서 multipart에 담긴 json데이터를 파싱 가능하도록 처리. (AbstractJackson2HttpMessageConverter를 상속해서 spring bean으로 등록해준다.)
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
/**
* Converter for support http request with header Content-Type: multipart/form-data
*/
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
-----------------------
위의 1, 2 방식중에서 HttpMessageConvert를 재정의하는것을 추천함. ==> 스웨거에서도 파일 업로드 가능.
1번의 방식을 사용하면, postman, curl로 파일업로드 테스트가 가능하지만, 스웨거에서는 파일 업로드가 불가함.
2번 방식으로 처리를 해야지, 스웨거에서도 파일 업로드 테스트가 가능하고, json 데이터를 multipart에 포함시켜서 받을수 있음.
[출처]
https://minoolian.github.io/tech/File-upload-error.html
[SpringBoot] DTO + MultipartFile 같이 사용시 오류
SpringBoot Controller의 핸들러메서드에서 게시글에 필요한 DTO 데이터와 이미지 저장을 위한 MultipartFile을 함께 사용시 발생한 오류에 대해 포스팅하려 한다.
minoolian.github.io