File Upload 트러블 슈팅(octet-stream이 뭔가요)

정인우·2022년 12월 6일
1

React Dev.

목록 보기
8/8
post-thumbnail

본 글은 다음과 같은 순서로 전개됩니다.

  • 에러 상황
  • 원인 분석
  • MIME Type이 뭔가요?
  • 바이너리 파일과 텍스트 파일
  • 다양한 Content-type

에러 상황

비디오 파일을 업로드하는 API CALL을 프로젝트의 특성상 진행해야 했는데, 계속 다음과 같은 오류가 발생했습니다.

Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]

해당 문제를 만들어 낸 코드는 아래와 같습니다.

export const uploadVideoFile = (videoSpaceId: number, videoFile: File) => {
  let videoFormData = new FormData();
  videoFormData.append('video', videoFile);
  videoFormData.append('videoInfo', { title: videoFile.name, description: videoFile.name });
  return axiosInstance.post<{ id: number }>(
    apiRoutes.uploadVideoFile.replace('{video-space-id}', `${videoSpaceId}`),
    videoFormData,
    {
      headers: { 'Content-Type': 'multipart/form-data' },
    },
  );
};

원인 분석

위와 같은 에러가 발생한 이유도 해당 에러도 당시에 잘 알지 못했습니다. 일단 'application/octet-stream' 자체를 처음 접하였고, 해당 코드 어디에도 그러한 설정을 진행한 것이 없기에 혼란스러웠습니다.

당시 일단 에러 코드는 415를 출력하고 있었습니다. 415 같은 경우 Unsupported Media Type으로 ajax를 통해 보내주는 media type과 서버에서 요구하는 media type이 불일치 시에 발생하는 에러입니다.

문제는 현재 uploadVideoFile에서 전송하는 두 가지의 데이터의 타입이 video/mp4와 application/json으로 날아오기를 서버는 기대하고 있는데, application/octet-stream으로 날아가기에 문제를 일으키는 듯 했습니다.

'application/octet-stream'

application/octet-stream 는 다른 모든 경우를 위한 기본값입니다. 알려지지 않은 파일 타입은 이 타입을 사용해야 합니다. 브라우저들은 이런 파일들을 다룰 때, 사용자를 위험한 동작으로부터 보호하도록 개별적인 주의를 기울여야 합니다.

타입을 지정하지 않았기에 기본적인 값으로 날아가 문제를 발생시키는 것으로 판단하고 아래와 같이 코드를 수정했습니다.

export const uploadVideoFile = (videoSpaceId: number, videoFile: File) => {
  let videoFormData = new FormData();
  const blob = new Blob([JSON.stringify({ title: videoFile.name, description: videoFile.name })], {
    type: 'application/json',
  });
  videoFormData.append('video', videoFile, { contentType: 'video/mp4' });
  videoFormData.append('videoInfo', blob, { contentType: 'application/json' });
  return axiosInstance.post<{ id: number }>(
    apiRoutes.uploadVideoFile.replace('{video-space-id}', `${videoSpaceId}`),
    videoFormData,
    {
      headers: { 'Content-Type': 'multipart/form-data' },
    },
  );
};

formdata 내에 json과 같은 형식을 넣어줄 때 blob으로 감싸고 type을 지정하여 기본 타입으로 지정되지 않도록 수정하였습니다. 이를 통해 문제를 해결할 수 있었습니다. 이와 함께 oct-stream 등이 포함된 MIME Type에 대해 조금 더 알아보도록 하겠습니다.

MIME Type이 뭔가요?

MIME 타입이란 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘입니다: 웹에서 파일의 확장자는 별 의미가 없습니다. 그러므로, 각 문서와 함께 올바른 MIME 타입을 전송하도록, 서버가 정확히 설정하는 것이 중요합니다. 브라우저들은 리소스를 내려받았을 때 해야 할 기본 동작이 무엇인지를 결정하기 위해 대게 MIME 타입을 사용합니다.

라고 MDN에서 설명하고 있지만 이것만으로는 잘 이해가 되지 않으니, 조금 더 설명해보도록 하겠습니다.

이전에는 텍스트 파일을 주고 받는데에 ASCII로 공통된 표준에 따르기만 하면 문제가 없었으나 네트워크를 통해 ASCII가 아닌 바이너리 파일을 보내는 경우가 생기게 되었습니다. 음악파일, 무비파일, 워드파일 등등 ASCII만으로는 전송이 안되기 때문에 기존 시스템에서 문제 없이 전달하기 위해서는 텍스트로의 변환이 필요했습니다.

위와 같이 바이너리 파일들을 텍스트 파일로 변환하는 것을 인코딩(Encoding), 텍스트 파일을 바이너리 파일로 변환하는 것을 디코딩(Decoding)이라고 합니다.

이러한 방식으로 인코딩한 파일은 Content-type정보를 앞부분에 담게되며 Content-type은 여러가지 타입이 있습니다.

여기까지 읽다보면 의문이 생길 수 있습니다. 바이너리 파일들을 ASCII들을 통해 보내는 게 왜 문제가 되나요?

바이너리 파일과 텍스트 파일

그 의문에 답하기 위해서는 바이너리 파일과 텍스트 파일에 대해서 조금 더 알아볼 필요가 있습니다.

우리가 많이 사용하는 jpg, png, exe 같은 파일들이 모두 바이너리 파일에 속합니다. 바이너리 파일은 사용자나 프로그램이 사용한 값들을 특별한 가공 없이 파일에 그래도 저장합니다. 만약에 프로그램이 73이라는 값을 생성한다면 어떠한 변화나 대체 없이 그대로 73이 기록됩니다. 바이너리 파일은 이러한 특징 때문에 해당 파일을 제대로 읽기 위해서 해당 파일을 읽을 수 있는 프로그램이 필요합니다. 43, 7, 63 이러한 숫자의 연속을 그대로 해석할 수 없기 때문입니다. png 파일 등을 메모장에서 열어보면 알 수 없는 문자들이 나타나는 것 또한 다음과 같은 이유입니다.

텍스트 파일은 위와는 조금 다릅니다. 텍스트 파일은 문자를 기반으로 하는 코드 값이 저장된 파일을 의미합니다. ASCII 기반의 텍스트 파일이라면 해당 파일에는 ASCII 형식의 문자열이 저장되어 있습니다. 텍스트 파일을 일정한 규약을 통해 변화시킨 값들이 저장되기에 해당 규약에 맞게 해석하면 됩니다.

그렇다면 이제는 왜 문제가 생기는지 이해할 수 있을 것입니다. 바이너리 파일들을 ASCII 처럼 하나의 규약으로 변환해서 읽을 수 있는 것이 아니라 값을 그대로 저장하기에 이를 해석하는 일에 문제가 생기고 이러한 문제를 해결하기 위해 MIME 인코딩이 필요하게 됩니다.

그리고 이러한 MIME 인코딩을 진행할 시에 인코딩을 진행하기 전 바이너리 파일의 형식에 대한 정보가 주어지게 되는데 이 것이 Content-type 입니다.

다양한 Content-type(MIME Type)

일단 이에 앞서 Content-type 문법에 대해 간단하게 알아보겠습니다.

일반적인 구조

type/subtype

MIME 타입의 구조는 매우 간단합니다 '/'로 구분된 두 개의 문자열인 타입과 서브타입으로 구성됩니다. 스페이스는 허용되지 않습니다. type은 카테고리를 나타내며 개별(discrete) 혹은 멀티파트 타입이 될 수 있습니다. subtype 은 각각의 타입에 한정됩니다.

MIME 타입은 대소문자를 구분하지는 않지만 전통적으로 소문자로 쓰여집니다.

구체적인 타입들에 대해 전부 설명드리고 싶지만 그렇다면 너무 길어지기에 여기서는 위에서 언급한 application/octet-stream과 관련된 부분만 다루도록 하겠습니다. 모든 MIME Type에 관한 글은 링크를 참고해주시길 바랍니다.

type 중 application 타입이 있습니다. 이는 모든 종류의 이진(바이너리) 데이터를 나타냅니다.

application/octet-stream,
application/pkcs12,
application/vnd.mspowerpoint,
application/xhtml+xml,
application/xml,
application/pdf

다음과 같이 서브 타입들이 존재합니다. 이 중에서 저희가 신경 써서 볼 것은 application/octet-stream입니다.

이 타입은 이진 파일을 위한 기본값입니다. 이 타입은 실제로 잘 알려지지 않은 이진 파일을 의미하므로, 브라우저는 보통 자동으로 실행하지 않거나 실행해야 할지 묻기도 합니다. Content-Disposition 헤더가 값 attachment 와 함게 설정되었고 'Save As' 파일을 제안하는지 여부에 따라 브라우저가 그것을 다루게 됩니다.

즉 application의 기본 타입으로 위와 같은 경우 바이너리 파일인데 추가적인 타입 정보를 제공하지 않았기에 기본 타입으로 설정되었다고 추측할 수 있습니다.

다른 MIME Type 같은 경우에도 아래의 참고나 위의 링크를 통해 전부 다 한 번쯤 읽어보시길 권장드리며 글을 마무리하도록 하겠습니다.

참고

MIME TYPE - HTTP

0개의 댓글