[Web] File과 JSON 동시에 서버로 전송하기(body 데이터에 각각 다른 Content-Type 적용하기

장유진·2023년 7월 14일
1

Web Development

목록 보기
4/5

Project Restoration - 댕근이다옹(DangGeuneDaong)

프로젝트에서 업로드 기능을 구현하면서 파일과 함께 JSON 데이터를 함께 보내야 할 상황을 마주하게 되었습니다. API 명세서에서 요구한 내용은 다음과 같습니다.

  1. body에 File을 리스트에 담아 보내고, Content-type은 multipart/form-data로 지정할 것
  2. body에 JSON 데이터를 오브젝트에 담아 보내고, Content-type은 application/json으로 지정할 것
{
	File:[] // 파일 객체
	request:{
		userId: 'geniee1220',
		mainCategory: 'DOG'
		...
	}
}

평소 서버에 데이터를 보낼 때 HTTP의 헤더에 일반적인 데이터(JSON 객체)를 보낼 때는 "Content-Type: application/json"을, 파일을 전송할 때는 "Content-Type: 'multipart/form-data” 을 사용해서 요청 본문(body)의 유형을 명시해주었는데요. 이런 경우에는 어떻게 해결해야 할까요?

Header의 Content-type 결정하기

주로 파일, 이미지, 오디오 등 멀티미디어 파일은 바이너리 데이터로 이루어져 있기 때문에, HTTP 에서 이미지나 파일을 올리고자 할 때는 new FormData() 객체를 생성한 다음 서버로 보내는 것이 일반적인 방법입니다.

이 때 HTTP 헤더의 Content-type에는 multipart/form-data 형식을 지정하는데, 파일 뿐만 아니라 일반적인 텍스트 데이터를 함께 전송할 수 있기 때문입니다.

반면에 Content-type을 application/json 으로 지정하는 경우 바이너리 데이터로 구성된 멀티미디어 파일은 전송할 수 없기 때문에 이번 경우처럼 파일과 문자열 데이터를 함께 보내야 하는 경우에는 Request Header에 multipart/form-data를 지정해야 한다는 결론을 도출할 수 있었습니다.

Body의 특정 데이터에 Content-type 설정하기

따라서 body의 JSON 타입 데이터에 헤더와는 다른 Content-type, application/data 지정해주는 것이 관건이라고 생각했습니다.

제가 주목한 것은, Blob(Binary Large Object, 블랍) 생성자의 options으로 MIME(Multipurpose Internet Mail Extensions) 타입을 지정해줄 수 있다는 것이었습니다.

❗️MIME 타입은 파일의 컨텐츠 유형을 나타내는 문자열로, 일반적으로 HTTP 요청 및 응답에서 Content-Type 헤더를 정의하는 데 사용됩니다.

✨ 완성된 최종 코드

export const addPost = async (data: PostModel) => {
  const formData = new FormData();

  try {
    // 데이터 객체에서 파일들(files)을 분리하고, 나머지 속성들(requestObject)을 추출합니다.
    const { files, ...requestObject } = data;

    // requestObject를 JSON 문자열로 변환하여 Blob 객체로 만듭니다.
    const requestBlob = new Blob([JSON.stringify(requestObject)], {
      type: 'application/json',
    });

    // FormData에 requestBlob을 추가합니다.
    formData.append('request', requestBlob);

    if (files.length == 0) {
      // 파일이 없는 경우, FormData에 빈 문자열을 추가합니다.
      formData.append('files', '');
    } else {
      files.forEach((file: File) => {
         formData.append('files', file);
      });
    }

    // instance를 사용하여 api 주소로 multipart/form-data로 요청을 보냅니다.
	// instance를 사용했기 때문에, 인증 토큰 입력 부분은 생략합니다.
    const response = await instance.post(
      'api',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
        onUploadProgress: (progressEvent) => {
          const percentCompleted = Math.round(
            ((progressEvent?.loaded ?? 0) * 100) / (progressEvent?.total ?? 1)
          );
          console.log(`Upload progress: ${percentCompleted}%`);
        },
      }
    );

    return response;
  } catch (error: any) {
    throw new Error(error);
  }
};


Reference
https://heropy.blog/2019/02/28/blob/
https://velog.io/@huewilliams/파일과-JSON-데이터를-동시에-보내기-1편-feat.Multipartform-data
https://velog.io/@st4889/React-첨부파일-업로드-다운받기-구현하기api로-전송#-axios로-첨부파일을-서버에-보내자

0개의 댓글