Multipart/form-data 파헤치기

Ava Kim·2023년 1월 29일
2

왜 파헤치게 되었는가

결론부터 말하자면, 아무리 찾아도 그 어디에도 설명이 없어서 🤯

multipart/form-data로 이미지 데이터를 전송 할 때는 아래와 같은 형태로 formData에 append 해야 한다는데, 어떤 공식문서에도 명시되어 있지 않고 이 형태를 지켜야 한다는 걸 대체 어디서 알았는지 출처를 밝힌 블로그 포스트도 없었다.

{ 
	uri: image_path,
	name: file_name,
	type: image_type
}

너무 답답한데!! 궁금한데!! 스프린트 돌려야하니까 내 궁금증은 고이 접어두고 일단 써서 해결할까 했지만, J님이 그러지 말라고 하셨다. 파헤쳐라!하셔서 그렇다면?😏 하고 열심히 파헤치러 갔다.

상황

RN 앱에서 이미지를 서버에 업로드 해야했다. 이미지는 파일이니까 일단 구글링을 해본다.

const data = new FormData();
data.append('my_photo', {
  uri: filePath, // your file path string
  name: 'my_photo.jpg',
  type: 'image/jpg'
}

출처: https://medium.com/20spokes-whiteboard/uploading-images-from-react-native-to-your-server-6539082f9051

새로운 formData 오브젝트를 생성한 뒤 필요한 data를 key - value 형태로 append 하면 된다고 한다. 그런데 RN에서 이미지를 업로드 할 때 반드시 uri, name, type 값이 있는 오브젝트를 append 해야 한다.

FormData는 뭔데?

궁금하니까 찾아 본다.

The FormData interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch() or XMLHttpRequest.send() method.

그런데 데이터를 append 할 때는 name과 value를 파라미터로 전달해야 하고, value 값으로는 string 또는 blob을 쓸 수 있다고 한다.

Parameters

name

The name of the field whose data is contained in value.

value

The field's value. This can be a string or Blob (including subclasses such as File). If none of these are specified the value is converted to a string.

출처: https://developer.mozilla.org/en-US/docs/Web/API/FormData

Blob은 뭔데?

궁금하니까 또 찾아 본다.

The Blob object represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.

Blobs can represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.

An iterable object such as an Array, having ArrayBuffers, TypedArrays, DataViews, Blobs, strings, or a mix of any of such elements, that will be put inside the Blob.

new Blob(array)
const array = ['<q id="a"><span id="b">hey!</span></q>']; // an array consisting of a single string
const blob = new Blob(array, { type: "text/html" }); // the blob

Blob은 iterable object 형태라는데, 왜 uri, name, path 값이 있는 오브젝트를 써야 하는지에 대한 답이 되지는 않는다.

출처: https://developer.mozilla.org/en-US/docs/Web/API/Blob

File 형태를 찾아보자

문서를 자세히 다시 읽어보니 File은 Blob의 subclass라니까 이번엔 File을 찾아본다.

MDN File API에서 File Constructor를 읽어보면 new File(bits, name, options)로 File 오브젝트를 생성할 수 있는데, bits에는 iterable한 오브젝트, 스트링이 들어가고, name에는 파일명 또는 경로, options에는 type과 lastModified를 설정할 수 있다. 오 이제 좀 uri, name, type과 비슷한 걸 찾은 것 같다.

출처: https://developer.mozilla.org/en-US/docs/Web/API/File/File

더 찾아보자

이미지 URIfmf JS File Object로 바꾸려면 data url을 Unit8Array로 바꿔주고 file name과 type을 지정해주면 된다는 스택오버플로우 글을 찾았다. 좀 더 비슷해진 것 같지만 아직 확신할 수 없다.

출처: https://stackoverflow.com/questions/43358456/convert-image-uri-into-javascript-file-object


더 구글링 하다가 이런 코멘트를 찾았다. facebook의 예시를 따랐다고 하니까 드디어 뭔가 찾은 건가?

I am currently using XHR to upload images with no issues. My implementation is based off of facebook's example https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/XHRExample.ios.js#L183-L230
Something like this:
formdata.append(photo, {type: "image/jpeg", name: ____, uri: ______ });

출처: https://github.com/react-native-image-picker/react-native-image-picker/issues/31


아,, 링크가 404다 😩 이제 한계가 찾아와서, 추측을 하기에 이르렀다. 누가 해주는지는 모르겠으나, { uri: image_path, name: file_name, type: image_type } 오브젝트 형태를 지켜야 하는 이유는 image uri를 file object로 변환하기 위함인 것 같다.

그래서 결론은!

나는 이제 더 이상 못 찾겠다 손 들었을 때 감사하게도 사수님,,, 상사,,, (그러고 보니 밖에서 뭐라고 불러야 할지 잘 모르겠는) J님께서 RN 코드를 직접 파헤쳐서 아래 코드를 찾아주셨다.

결론은 RN이 이렇게 쓰도록 정의해 놓았다는 거다. 많이 허무해 🤯 근데 웃긴 건 코멘트에는 분명 filename과 content type이 ‘optional’이라고 쓰여있지만 안 쓰면 에러난다. PR 날릴까?,,,🤨

출처: https://github.com/facebook/react-native/blob/main/Libraries/Network/FormData.js

// The body part is a "blob", which in React Native just means
// an object with a `uri` attribute. Optionally, it can also
// have a `name` and `type` attribute to specify filename and
// content type (cf. web Blob interface.)
if (typeof value === 'object' && !Array.isArray(value) && value) {
  if (typeof value.name === 'string') {
    headers['content-disposition'] += '; filename="' + value.name + '"';
  }
  if (typeof value.type === 'string') {
    headers['content-type'] = value.type;
  }
  return {...value, headers, fieldName: name};
}
// Convert non-object values to strings as per FormData.append() spec
return {string: String(value), headers, fieldName: name};
    });
  }
}

오늘의 교훈

뭐든 왜 그렇게 써야 하는지 생각해보지 않고, ‘그냥 그렇게 쓰래’ 하고 넘어가지 말자. 궁금한 건 끝까지 파헤치고, 그것도 안 되면 사수를 부여잡고 애원해서라도 (애원하지 않았음) 알아내는 개발자가 되자💪🏼
역시 글쓰기는 어렵다,,,

formData 관련해 더 정확한 정보를 아신다면 댓글로 공유해 주세요🤩

profile
FE developer | self-believer | philomath

0개의 댓글