프로젝트를 진행하면서 프론트 -> 백엔드로 이미지를 전송하는 경우가 있었다.
오늘은 HTTP, multipart, multipart/form-data 세 가지 키워드에 대해 알아보고,
그 중에서 중요한 개념중에 하나인 multipart/form-data
에 대해서 좀 더 깊이 알아보기로 한다.
인터넷 상에서 클라이언트와 서버가 자원을 주고 받을 때 쓰는 통신 규약.
- 파일 업로드를 구현할 때, 클라이언트가 웹브라우저라면 폼을 통해서 파일을 등록해서 전송한다.
- 웹 브라우저가 보내는 HTTP 메시지는
Content-Type
속성이multipart/form-data
로 지정되고 정해진 형식에 따라 메시지를 인코딩하여 전송한다.- 이를 처리하기 위한 서버는 멀티파트 메시지에 대해서 각 파트별로 분리하여 개별 파일의 정보를 얻게 된다.
이미지 파일을 전송한다고 해서 이메일에 첨부파일을 붙여 메일을 보내는 것처럼 png나 jpg 파일 자체가 전송되는 것이 아니다.
이미지 파일도 문자로 이뤄져 있기 때문에 이미지 파일을 문자로 생성하여 HTTP request body에 담아 서버로 전송하는 것이다.
HTTP(request와 response)는 위 이미지와 같이 4개의 파트로 나눌 수 있다.
여기서 Message Body에 들어가는 데이터 타입을 HTTP Header에 명시해줄 수 있다.
이 때 명시할 수 있도록 해주는 필드가 바로 Content-type
이다.
추가적으로 Content-type 필드에 MIME(Multipurpose Internet Mail Extensions) 타입을 기술해줄 수 있는데, 여러 타입 중 하나가 바로 multipart
이다.
form은 입력 양식 전체를 감싸는 태그이다.
name
: form의 이름, 서버로 보내질 때 이름의 값으로 데이터 전송한다.
action
: form이 전송되는 서버 url 또는 html 링크.
method
: 전송 방법 설정. get
은 default
,
post는 데이터를 url에 공개하지 않고 숨겨서 전송하는 방법이다.
autocomplete
: 자동 완성. on으로 하면 form 전체에 자동 완성 허용한다.
enctype
: 폼 데이터(form data)가 서버로 제출될 때
해당 데이터가 인코딩되는 방법을 명시한다.
그 중 오늘의 포스트는 entype의 속성값 중 하나이다.
이 속성은 <form>
요소의 method 속성값이 post
인 경우에만 사용할 수 있다.
application/x-www-form-urlencoded
default 값으로, 모든 문자들을 서버로 보내기 전에 인코딩됨을 명시한다.
text/plain
공백 문자(space)는 “+” 기호로 변환하지만, 나머지 문자는 모두 인코딩되지 않음을 명시한다.
multipart/form-data (오늘의 중요 포인트!)
모든 문자를 인코딩하지 않음을 명시한다.
이 방식은<form>
요소가 파일이나 이미지를 서버로 전송할 때 주로 사용한다.
<form action="/home/uploadfiles" method="post" enctype="multipart/form-data">
파일명 : <input type="file" name="myfile">
<button type="submit">제출하기</button>
</form>
HTML의 input 의 type 중에 "file"이 있다.(이를테면 <input type="file" />
)
이건 파일 업로드를 위한 input 컨트롤이다. 즉 사용자 컴퓨터에서 서버로 파일을 전송하기 위한 input 이다.
form이 submit이 되면 form안에 있는 컨트롤들의 데이터가 서버로 전송된다. 이때 이런 데이터들은 HTTP Request 형태로 서버로 전송된다. 파일 업로드의 원리는 HTTP Request가 가지고 있는데,
그 원리는 아래와 같다.
HTTP Request는 Body에 클라이언트가 전송하려고 하는 데이터를 넣을 수 있다.
Body에 들어가는 데이터의 타입을 HTTP Header에 명시해 줌으로써 서버가 타입에 따라 알맞게 처리하게 한다.
이 Body의 타입을 명시하는 Header가 Content-type이다.
그런데 여기서 중요한 점은 보통 HTTP Request의 Body는 한 종류의 타입이 대부분이고, Content-type도 타입을 하나만 명시할 수 있다.
ex) text이면 text/plain, xml이면 text/xml, jpg이미지면 image/jpeg 등
일반적인 form의 submit에 의한 데이터들의 Content-type
은 application/x-www-form-urlencoded
이다.
그런데 파일 업로드의 상황을 살펴보면, 만약 자신이 찍은 사진을 올리는 form
의 경우, 사진에 대한 설명을 위한 input
과 사진 파일을 위한 input
2개가 들어간다.
그런데 이 두 input 간에 Content-type
이 전혀 다르다.
이 두 input
간에 Content-type
은 사진 설명은 application/x-www-form-urlencoded
이 될 것이고,
사진 파일은 image/jpeg
이다.
두 종류의 데이터가 하나의 HTTP Request Body
에 들어가야 하는데,
한 Body
에서 이 2 종류의 데이터를 구분에서 넣어주는 방법도 필요해졌다.
그래서 등장하는 것이 multipart
타입이다.
위에서 이론을 이야기했다면, 이번엔 실제 코드를 통해 어떻게 이미지를 업로드 시키는지 알아보도록 하자
<form
name="photo"
encType="multipart/form-data"
onSubmit={handleSubmit}
>
<input
type="file"
name="photo"
accept="image/*,audio/*,video/mp4,video/x-m4v,application/pdf,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,.csv"
onChange={handleUpload}
/>
...
이 form이 제출될 때 어떤 형식인지를 알려주는 것이다. 그리고 input에 필요한 이벤트핸들러를 만들어 주면 된다. 아 그 전에 어떤 파일들이 올라갈지 accept라는 attribute를 지정해줘야 한다.
const handleSubmit = async(e) => {
e.preventDefault();
const formData = new FormData();
formData.append("photo", files.length && files[0].uploadedFile);
formData.append("comment", commentValue);
formData.append("content_id", classData.content_id);
await axios({
method: "post",
url: process.env.REACT_APP_STREAMING_COMMENT_URL, //환경변수
data: formData,
headers: { "Content-Type": "multipart/form-data", Authorization: localStorage.getItem("access_token") }
});
setCommentValue("");
setFiles([]);
};
const handleUpload = (e) => {
e.preventDefault();
const file = e.target.files[0];
setFiles([...files, { uploadedFile: file }]);
};
위에서 언급했듯이 multipart/form-data
는 POST
로 보내줘야 하기 때문에 method는 POST
로 해준다.
그리고 handleUpload 함수에 보면 const file = e.target.files[0]이라고 선언된 것을 볼 수 있지만 e.target.file은 배열이 아니다.
//e.target.file
fileList: {
0: {name: file1, ...},
length: 1,
item: {...}
}
e.target.file
은 이렇게 생겼고 우리가 필요한 것은 파일이기 때문에 0라는 키에 접근한 것이다.
<참고자료>
HTTP multipart/form-data 이해하기
Multipart/form-data란?
React - axios를 이용해 댓글에 이미지 업로드 해보기
찾던 내용이었는데 단번에 해결되었네요 감사합니다