파일 업로드를 재개하는 방법

이 글은 객원 기고가인 사이몬 스파이크가 작성한 글입니다. 사이몬은 웹 개발자이며, 웹 표준 신봉자이자 모질라 0.8(!)부터 모질라 애호가입니다.

이번 글은 사이먼이 파일 API와 Firefox 4에서 새롭게 소개된 slice() 메소드를 가지고 실험한 내용입니다. 여기에 사이먼이 구현한 파일 업로드를 재개방법이 설명돼 있습니다.

XHR 레벨 2 객체를 사용하면 파일을 업로드 할 수 있습니다. 이 객체는 요청(예: 파일 전송, 전송 과정 확인)과 응답(예: 업로드가 성공했는지 아니면 오류가 발생했는지)을 다루는 서로 다른 이벤트와 메소드를 제공합니다. 더 자세한 정보는 HTML5 이미지 업로더 개발 방법을 읽어보시기 바랍니다.

아쉽게도, XHR 객체는 업로드를 중단하거나 재개하는 메소드를 제공하지 않습니다. 하지만, 새로운 File API의 slice() 메소드와 XHR의 abort() 메소를 조합하면 이를 구현할 수 있습니다. 그 방법을 함께 알아 봅시다.

라이브 데모

파일 업로드 라이브 데모를 여기에서 확인하거나, github.com에서 자바스크립트와 PHP 코드를 다운로드 할 수 있습니다.

업로드 중단과 재개

기본적인 생각은 사용자에게 진행중인 업로드를 중단하고 후에 다시 재개할 수 있는 버튼을 제공하는 것입니다. 요청을 중단하는 것은 간단합니다. 간단히, abort()를 메소를 통해 요청을 중단합니다. 다만, 주의할 점은 요청을 중단할 때, 사용자 인터페이스에서 이를 오류로 표시하지 않아야 한다는 것입니다.

더 어려운 부분은 요청이 중단 되고 연결이 닫힌 후에 업로드를 재개하는 부분입니다. 파일 전체를 다시 전송하는 대신에 blob의 slice() 메소를 사용하여 파일의 나머지 부분을 포함하는 덩어리(chunk)를 만들 것입니다. 그런 후에 새로운 요청을 만들고, 그 덩어리를 전송하며 이를 이전 요청이 중지 됐을 때 서버에 저장된 부분에 추가할 것입니다.

나머지 부분(chunk) 만들기

파일 나머지 부분(chunk)은 다음과 같이 만들 수 있습니다.

var chunk = file.slice(start, length);

파일을 자르기 위해 필요한 정보는 자를 시작점입니다. 즉, 이미 업로드된 바이트 수를 알아야 합니다. 가장 쉬운 방법은 요청을 중단하기 전에 ProgressEvent의 loaded 속성의 값을 저장하는 것입니다. 하지만, 이 값은 실제 서버에서 저장된 바이트 수와 일치하지 않을 수 있습니다. 가장 신뢰있는 접근 방법은 추가적인 요청을 통해 서버에 실제 저장된 바이트 수를 받는 것입니다. 그런 후에 이 정보를 가지고 파일을 자르고 전송할 덩어리(chunk)를 만듭니다.

흐름 정리

(업로드가 이미 진행 중이라고 가정합니다)

  1. 사용자가 업로드를 멈춘다
  2. UI 상태는 멈춤 상태로 설정된다
  3. 업로드를 중단한다
  4. 서버에서 디스크에 파일 쓰는 작업이 중단된다
  5. 사용자가 업로드를 재개한다
  6. UI 상태는 재개로 설정된다
  7. 서버로부터 이미 저장된 파일 크기를 얻는다
  8. 파일의 남은 부분을 자른다(덩어리)
  9. 남은 부분(chunk)을 업로드한다
  10. UI 상태를 업로드 중으로 설정한다
  11. 서버는 데이터를 추가로 붙인다

자바스크립트 코드

// 방금 막 이미 써진 바이트 수를 알기위해 요청했고,
// xhr.result에 서버의 응답이 xhr.result에 담겨 있다고 가정.
var start = xhr.result.numWrittenBytes;
var length = file.size - start;

var chunk = file.slice(start, length);   
 
var req = new XMLHttpRequest();

req.open('post', 'fnc.php?fnc=resume', true);
 
req.setRequestHeader("Cache-Control", "no-cache");

req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
req.setRequestHeader("X-File-Name", file.name);

req.setRequestHeader("X-File-Size", file.size);
 
req.send(chunk);

PHP 코드

서버에서 일반적인 업로드와 재개된 업로드를 다루는데 있어 유일하게 다른 점은, 후자의 경우 파일을 새로 생성하는 대신에 기존의 파일에 데이터를 추가한다는 것입니다.

$headers = getallheaders();
$protocol = $_SERVER[‘SERVER_PROTOCOL’];

$fnc = isset($_GET['fnc']) ? $_GET['fnc'] : null;

$file = new stdClass();
$file->name = basename($headers['X-File-Name']));

$file->size = $headers['X-File-Size']);
 
// php://input 은 php.ini 설정을 무시하기 때문에, 파일 크기를 직접 제한해야함 
$maxUpload = getBytes(ini_get('upload_max_filesize'));

$maxPost = getBytes(ini_get('post_max_size'));
$memoryLimit = getBytes(ini_get('memory_limit'));

$limit = min($maxUpload, $maxPost, $memoryLimit);
if ($headers['Content-Length'] > $limit) {

  header($protocol.' 403 Forbidden');
  exit('File size to big. Limit is '.$limit. ' bytes.');

}
 
$file->content = file_get_contents(’php://input’);
$flag = ($fnc == ‘resume’ ? FILE_APPEND : 0);

file_put_contents($file->name, $file->content, $flag);
 
function getBytes($val) {

 
$val = trim($val);
      $last = strtolower($val[strlen($val) - 1]);

      switch ($last) {
          case 'g': $val *= 1024;

 
case 'm': $val *= 1024;
 
case 'k': $val *= 1024;

      }
 
return $val;
}

Caution!

주의!

위에 있는 PHP 코드 예제는 보안 관련 부분이 전혀 업습니다. 사용자는 어떠한 유형의 파일이라도 여러분의 서버에 쓰거나, 붙여 쓰거나, 덮어 쓸 수 있습니다. 그러므로, 여러분의 웹 사이트에 업로드 기능을 사용할 때는 적절한 보안 정도를 분명히 적용해야 합니다.

오류가 발생한 후에 업로드 재개

업로드 중단 및 재개 흐름은 네트워크 오류가 발생한 후에 업로드를 계속하고자 할 때도 사용할 수 있습니다. 전체 파일을 다시 업로드 하는 대신에 서버로부터 이미 쓰여진 파일 크기를 받고 남은 크기 만큼을 덩어리로 자릅니다.

중지 했거나 전송이 끊긴 파일 업로드를 재개할 때 기억할 점

요청이 중단됐을 때 서버에서 뭘 쓰는지 안 쓰는지까지 모두 제어할 수 없기 때문에, 이미 올라간 파일에 데이터를 추가하는 작업은 파일을 깨지게 할 수 있습니다.

브라우저 충돌 후에 업로드 재개

업로드 중단 및 재개 기능을 보다 나아가서 사용할 수 도 있습니다. (적어도 이론적으로) 브라우저가 뜻하지 않게 종료되거나 충돌이 일어나 업로드가 중단된 경우에도 이를 복구하는 것이 가능합니다. 문제점은 브라우저가 닫힌 후에는 메모리로 읽어들인 파일 객체가 사라진다는 것입니다. 그러므로, 업로드 재개를 위해 파일을 자르기 전에 사용자가 먼저 파일을 다시 선택하거나 드래그 해야 합니다.

다른 방법으로, 새로운 IndexedDB API를 사용하여 업로드가 끝나기 전에 파일을 저장할 수 있습니다. 그러면, 브라우저 충돌이 일어난 후에 데이터베이스로부터 파일을 읽어와 업로드 안 된 부분을 자르고 업로드를 재개할 수 있습니다.

원저자: Paul Rouget – 원문으로 가기

작성자: Kim, Myung Shin

Kim, Myung Shin가 작성한 문서들…


댓글이 없습니다.

댓글 쓰기