JS - 파일API : 파일 업로딩 구현
과거에는 사용자가 파일을 탐색해서 업로드할 파일을 선택하고, 파일 입로드가 완료되어 페이지가 갱신될 때까지 오랫동안(진행 상황을 알려주는 표시나 피드백 없이) 기다려야 했는데 이는 굉장히 불편한 부분일 것입니다. 다행히 XHR은 두 가지 문제를 해결해 줍니다. XHR은 백그라운드로 파일을 업로드하면서 동시에 진행 이벤트도 제공하여 사용자에게 실시간 프로그레스바를 보여줄 수 있다. 주요 브라우저는 XHR을 지원합니다.
기존의 XMLHttpRequest API의 send() 함수나 FormData 인스턴스를 이용해 파일을 업로드 할 수 있습니다. FormData 인스턴스는 폼의 컨텐츠를 조작하기 쉬운 인터페이스로 표현한 것입니다. FormData 오브젝트를 직접 만들 수도 있고 오브젝트를 인스턴스화할 때 기존의 form 엘리먼트를 전달해서 만드는 방법도 있습니다.
var formData = new FormData($("form")[0]);
// 폼 데이터를 문자열로 추가 가능합니다.
formData.append("stringkey", "stringData");
// 파일 오브젝트도 추가할 수 있습니다.
formData.append("fileKey", file);
FormData를 완성했으면 XMLHttpRequest를 이용해 서버로 POST할 수 있습니다. jQuery로 Ajax 요청을 이용하는 경우이면 jQuery가 데이터를 직렬화하지 않도록 processData 옵션을 false로 설정해야 합니다. 브라우저가 Content-Type 헤더를 자동으로 multipart/form-data로 설정(multipart/form-data의 컨텐츠는 multipart boundary 로 파트를 구분)하므로 Content-Type 헤더는 설정하지 않습니다.
jQuery.ajax({
data: formData,
processData: false,
url: "http://example.com",
type: "POST"
});
FormData 의 대안으로 파일을 직접 XHR 오브젝트의 send() 함수로 전달하는 방법도 있습니다.
var req = new XMLHttpRequest();
req.open("POST", "http://example.com", true);
req.send(file);
또는 jQuery의 Ajax API로 다음과 같이 파일을 업로드할 수 있습니다.
$.ajax({
url: "http://example.com",
type: "POST",
success: function(){ /* ... */ },
processData: false,
data: file
});
위 방법은 기존의 multipart/form-data 방법과는 다르다는 점에 유의해야 합니다. 보통은 파일 이름 등의 정보를 업로드에 포함합니다. 그러나 위 예제는 순수하게 파일 데이터만 업로드합니다.
파일 정보를 건달하려면 X-File-Name 같은 커스텀 헤더를 설정해야 합니다. 그러면 우리가 만든 서버는 커스텀 헤더를 읽어서 파일 속성을 처리할 수 있습니다.
$.ajax({
url: "http://example.com",
type: "POST",
success: function(){ /* .., */ },
processData: false,
contentType: "multipart/form-data",
beforeSend: function(xhr, settings){
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("X-File-Name", file.fileName);
xhr.setRequestHealer("X-File-Size", file.fileSize);
},
data: file
});
불행히도 순수 데이터 업로드는 멀티파트나 URL, 인코딩 형식 파라미터보다 인지도가 떨어지는 방식이므로 많은 서버에서 문제가 발생할 수 있습니다. 위 방법을 사용한다면 요청을 직접 파싱해야 할지도 모릅니다. 따라서 multipart/form-data를 요청하는 방법으로 직렬화된 데이터를 FormData 오브젝트를 이용해 업로드하는 방법을 권장합니다.
Ajax 진행
XHR 레벨 2 규격 명세는 내려받기와 업로드 요청 모두에 progress 이벤트 지원을 추가했습니다. progress 이벤트를 이용하면 실시간 파일 업로드 프로그레스바로 사용자에게 업로드가 언제쯤 종료될지 등의 정보를 제공할 수 있습니다.
XHR 인스턴스에 리스너를 추가하면 내려받기 요청에서 발생하는 progress 이벤트를 받을 수 있습니다.
var req = new XMLHttpRequest();
req.addEventListener("progress", updateProgress, false);
req.addEventListener("load", transferComplete, false);
req.open();
XHR 인스턴스의 upload 속성에 이벤트 리스너를 추가하면 업로드 요청에서 발생하는 progress 이벤트를 받을 수 있습니다.
var req = new XMLHttpRequest();
req.upload.addEventListener("progress", updateProgress, false);
req.upload.addEventListener("load", transferComplete, false);
req.open();
업로드 요청이 끝난 다음 서버가 응답을 보내기 전에 load 이벤트가 발생합니다. beforeSend 콜백으로 XHR 오브젝트와 설정을 전달하므로 load 이벤트를 jQuery에 추가할 수 있습니다.
아래는 커스텀 헤더를 포함한 예제입니다.
$.ajax({
url: "http://example.com",
type: "POST",
success: function(){ /* ... */ },
processData: false,
dataType: "multipart/form-data",
beforeSend: function(xhr, settings){
var upload = xhr.upload;
if(settings.progress){
upload.addEventListener("progress", settings.progress, false);
}
if(settings.load){
upload.addEventListener("load", settings.load, false);
}
var fd = new FormData;
for(var key in settings.data){
fd.append(key, settings.data[key]);
}
settings.data = fd;
},
data: file
});
progress 이벤트는 업로드의 position(몇 바이트를 업로드 했는지를 가리키는 값)과 total(업로드 요청의 전체 크기를 바이트로 표현한 값)을 포함합니다. 이 두 프로퍼티를 이용해 진행 퍼센트를 계산할 수 있습니다.
var progress = function(event){
var percentage = Math.round((event.position / event.total) * 100);
// 프로그레스바 설정
};
이벤트는 타임스탬프를 포함하므로 업로드를 시작한 시간을 기록했다면 예상 완료 시간(ETA, Estimated Time of Arrival)을 만들 수 있습니다.
var startStamp = new Date();
var progress = function(e) {
var lapsed = startStamp - e.timeStamp;
var eta = lapsed * e.total / e.position - lapsed;
};
그러나 소량의 데이터를 업로드(파일 크기가 작은 데이터)할 때는 예상 시간이 정확하지 않을 수 있으므로 4분 이상 걸리는 큰 파일 업로드에만 ETA를 보여주는 것도 한 방법입니다. 아니면 업로드가 언제 끝날지를 시각적으로 보여줄 수 있는 퍼센트바(percentage bar)를 이용하는 것도 괜찮은 방법이 될 수 있습니다.
0 댓글