프론트측이 남긴 에러를 보니 분명 잘 코드를 작성한거같은데 500에러가 발생하고 있었다.
실제로 테스트를 해보니
다음과 같은 에러가 발생하고 있었다. application/octet-stream?? 처음 들어봤고 어떻게 생긴 에러인지 알 수가 없었다.
우선 에러가 나는 코드를 봐보자
멀티파트로 받아서 dto와 여러파일들을 저장하는 로직이다. 이 코드말고 multipartform을 적용한 모든 곳에서 발생하고 있었다.
근데 갑자기 왜 application/octet-stream을 지원하지 않는다는 에러가 나올까?
gpt선생님께 물어보자
정리하자면 지금 적용하고 있는 multipart/form은 다음과 같은 특징이 있다.
multipart/form-data
특징
- 데이터를 여러 부분으로 나누어 전송합니다.
- 각 부분은 자체적으로 헤더와 내용을 포함하며, 파일과 텍스트 데이터를 함께 전송할 수 있습니다.
- Boundary라는 구분자를 사용하여 각 부분을 나눕니다.
application/octet-stream
특징
- 데이터를 순수 바이너리 형식으로 전송합니다.
- 데이터에 별도의 구분자나 구조가 없습니다. 전송된 데이터는 순수한 바이너리 스트림으로 간주됩니다.
아니 근데 어째서 저녀석이 나온걸까?
다시 순차적으로 생각해보자
아래 구조를 보니 뭔가 servlet에서 핸들러 어댑터로 가고 argumentResolver에서 뭔가 처리를 해주지 않을까라는 생각이 들었다.
그래서 로그를 찍기위해 다음과 같은 코드를 필터에 적용해봤다.
@Slf4j
public class ContentTypeLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestContentType = request.getContentType();
log.info("Request Content-Type: " + requestContentType);
try {
if (request.getParts() != null) {
for (var part : request.getParts()) {
log.info("Part Name: " + part.getName());
log.info("Part Content-Type: " + part.getContentType());
}
}
} catch (Exception e) {
log.error("Error while logging parts: " + e.getMessage(), e);
}
filterChain.doFilter(request, response);
}
}
단순히 넘어올때 이름, content-Type 을 출력하게 하는 필터를 생성하고 적용시켜준다.
적용시키는건 SecurityConfig에 필터를 넣어주면 된다.
http
.addFilterBefore(new ContentTypeLoggingFilter(), JWTFilter.class);
로그를 찍어보니
둘다 따로 지정하지 않고 보내니 Content-Type이 null로 찍히는걸 볼 수 있었다.
이로써 ArgumentResolver에서 뭔가 작업이 있다는걸 알 수 있었고 코드를 찾아봤다.
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
...
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
}
실제로 찾아보니 AbstractMessageConvertMethodArgumentResolver에서 contentType이 null이면 application/octet-stream을 넣어주는 걸 볼 수 있었다. 근데 왜 스프링은 이걸 넣어주는 것일까?
궁금해서 찾아보니 MEME 표준이라고 한다.
https://zzandoli.tistory.com/60
우리가 흔히아는 ContentType application/json 이런 것도 MEME의 일종인데 MIME 표준에서 알 수 없는 바이너리 데이터를 나타내는 기본 타입이라고 한다.
그래서 우리는 application/octet-stream이 지원하지 않는다는 것을 받을 수 있던 것이었다..
이걸 해결해주기 위해 argumentResolver에 application/octet-stream를 처리해주는 Converter을 넣어주면 된다.
기본적으로 처리해주는 Jackon2HttpMessageConverter를 상속받아 application/octet-stream를 처리해주는 Converter을 넣어준다. write를 false처리해준 이유는 기본적으로 스프링에 ByteArrayHttpMessageConverter가 있어 읽기 작업은 가능하기때문이다. 그래서 application/octet-stream인지도 아는것. 그래서 우리는 쓰기 작업 Converter만 넣어주면 된다.
@Component
public class OctetStreamReadMsgConverter extends AbstractJackson2HttpMessageConverter {
@Autowired
public OctetStreamReadMsgConverter(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;
}
}
이녀석을
@Configuration
public class WebConfig implements WebMvcConfigurer {
private OctetStreamReadMsgConverter octetStreamReadMsgConverter;
@Autowired
public WebConfig(OctetStreamReadMsgConverter octetStreamReadMsgConverter) {
this.octetStreamReadMsgConverter = octetStreamReadMsgConverter;
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(octetStreamReadMsgConverter);
}
}
extendMessageConverters로 추가해주면 된다. 근데 configureMessageConverters로 많이 추가하던데 이걸 쓰게되면 swagger에서 오류가 발생해 extend를 사용했다. 차이는 아래와같다.
- configureMessageConverters: 기본 컨버터를 모두 제거하고 새로 설정 → 기존의 중요한 컨버터(ByteArrayHttpMessageConverter 등)가 사라짐.
- extendMessageConverters: 기본 컨버터를 유지하고 추가 설정 → 기존 컨버터가 유지되므로 문제 없음.
그러면 자라란
Content-Type이 null이어도 아주 잘 쿼리가 들어오는 것을 볼 수 있다.
'Project' 카테고리의 다른 글
[Project] 세종대학교 auth & 예약 시스템 (1) | 2025.01.17 |
---|---|
[Project] 온프레미스 서버 구축기 (0) | 2025.01.16 |
[Project] Docker 적용기 (8) | 2024.10.16 |