프로젝트에서 파일 업로드와 다운로드를 구현하면서 사용한 코드들의 의미를 알아보기 위해 정리한 글입니다.
flow :
file input -> 파일 선택 -> formdata 객체 만들기 -> multipart로 전송
<input type="file"
id="avatar" name="avatar"
accept="image/png, image/jpeg">
file type은 input 요소의 공용 특성 외에도 추가적인 특성을 지원한다.
accept
는 선택할 수 있는 파일 유형을 지정할 수 있다. 확장자(.jpg,.pdf,.doc)나 MIME 유형의 문자열을 값으로 입력하면 해당 유형의 파일만 선택할 수 있다.
아래처럼 입력하면 엑셀파일만 선택할 수 있다.
accept='application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
capture
는 이미지 또는 비디오 데이터를 캡처할 때 사용할 방법이고, files
는 선택한 파일을 나열하는 FileList다. multiple
을 true
로 지정하면 여러 개의 파일을 선택할 수 있다.
선택한 파일에는 아래와 같이 접근할 수 있고,
const selectedFile = document.getElementById('input').files[0];
onChange event를 이용해 e.target.files[0]
로도 접근할 수 있다.
FormData 인터페이스는 form 필드와 그 값을 나타내는 일련의 key/value 쌍을 쉽게 생성할 수 있는 방법을 제공한다. 또한 XMLHttpRequest.send() 메서드를 사용해 쉽게 전송할 수 있다.
리액트에서 formData로 파일을 보내는 코드를 아래처럼 짰다.
const [targetFile, setTargetFile] = useState<RcFile[]>([]);
const handleFileChange = (e)=>{
const file = e.target.files[0]
setTargetFile(file)
}
const onSubmit = ()=>{
const formData = new FormData();
formData.append('fileKey',targetFile[0])
axios.post("api/uploadfile", formData)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
}
...
<input type="file" name="file" onChange={e => this.handleFileChange(e)}/>
Multipart란 웹 클라이언트가 요청을 보낼 때 http 프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것을 의미한다. 여러 부분을 하나의 복합 메세지로 보낸다.
form을 통해서 파일을 등록하고 전송할 때 웹 브라우저가 보내는 HTTP 메시지는 Content-Type 속성이 multipart/form-data
로 지정되며, 정해진 형식에 따라 메시지를 인코딩하여 전송한다. 이를 처리하기 위해 서버는 멀티파트 메시지에 대해서 각 파트별로 분리하여 개별 파일의 정보를 얻게 된다.
HTTP는 위 이미지와 같이 4개의 파트로 나뉘고
Header의 Content-Type 필드로 message body에 들어가는 데이터 타입을 명시한다.
multipart 메시지는
Content-type
헤더에 boundary
파라미터를 포함한다boundary
는 메시지 파트를 구분하는 역할을 하며, 메세지의 시작과 끝 부분도 나타낸다파일 업로드를 진행하던 중
504 Gateway Time-out
에러가 발생했다. 파일 용량이 커서 nginx의 기본 timeout을 넘어갔기 때문에 발생하는 에러였다. 서버와 클라이언트간 connection을 유지하는 시간이 길기 때문이고 nginx의 기본 timout 시간을 바꿔주면 에러는 해결된다.
우리는 서버에서 bull library를 이용해 queue를 만들어 파일부터 전송하고, 업로드 과정은 클라이언트와의 connection과는 상관없이 처리하는 방식으로 구현했다.
flow :
api 요청 -> response를 blob으로 받아옴 -> 브라우저에서 다운로드
Blob객체는 파일류의 불변하는 미가공 데이터를 나타낸다. 텍스트와 이진 데이터의 형태로 읽을 수 있으며, ReadableStream 으로 변환한 후 그 메서드를 사용해 데이터를 처리할 수도 있다.
Javascript에서 File 인터페이스는 사용자 시스템의 파일을 지원하기 위해 Blob 인터페이스를 확장한 것이므로, 모든 Blob 기능을 상속한다.
const blob = new Blob(array,options)
const blob = new Blob([response.data], { type: response.headers['content-type'] });
array : ArrayBuffer, ArayBufferView,Blob(file),DOMString 객체 또는 이러한 객체가 혼합된 Array를 사용할 수 있다.
option
- type : 데이터의 MIME 타입, 기본값은 ""
- endings : \n을 포함하는 문자열 처리를 'transparent'와 'native'로 지정. 기본값은 transparent
Property는 size
type
를 가지고, slice(start,end,type)
를 사용해 지정된 바이트 범위의 데이터를 포함하는 새로운 Blob 객체를 만들 수 있다. 이를 Chunks 객체
라고 한다.
createObjectURL
: Blob 객체를 나타내는 URL을 포함한 다음과 같은 DOMString을 생성. 이 Blob URL은 생성된 window의 document(브라우저)에서만 유요하다revokeObjectURL()
: URL.createObjectURL()을 통해 생성한 기존 URL을 폐기한다. revokeObjectURL을 통해 해제하지 않으면 자바스크립트 엔진에서 GC되지 않는다. 메모리 누수를 방지하기 위해 생성된 URL을 DOM과 바인딩한 후에는 해제하는 것이 좋다.revokeObjectURL
export const downloadFile = (response: AxiosResponse<Blob>) => {
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
const filename = response.headers['content-disposition']
.split('filename=')[1]
.split(';')[0];
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
};
reference