Přejít k navigační liště

Zdroják » Různé » Jak navázat na přerušený nebo poškozený upload souboru

Jak navázat na přerušený nebo poškozený upload souboru

Články Různé

Upload souborů přes webové rozhraní není příliš komfortní – strohý dialog pro výběr souborů, donedávna možnost vybrat jeden soubor v dialogu, téměř nulová schopnost interakce – to vše způsobilo, že webdesignéři sahali raději k řešení ve Flashi či Javě. Nové prohlížeče se snaží nabídnout větší komfort – dnešní článek je toho ukázkou.

Autor článku vyzkoušel nové File API a metodu slice(), která byla představena ve Firefoxu 4. V tomto článku popisuje, jak navázat na přerušený upload souboru.

Upload souboru probíhá přes objekt XHR Level2. Ten poskytuje různé metody a události pro zpracování požadavku (například pro odesílání dat a průběžnou kontrolu jejich odesílání) a obsluhu odpovědi (jestli proběhl upload v pořádku, nebo se vyskytla chyba). Více informací naleznete v článku Upload obrázků pomocí HTML5.

XHR objekt naneštěstí nepodporuje metody pro pozastavení a následné navázání uploadu. Tuto funkci je ale možné implementovat kombinací nové metody slice() ve File API s abort()  v XHR. Pojďme se podívat, jak na to.

Autorem článku je Simon Speich — webový vývojář, nadšenec do webových standardů a milovník Mozilly od verze 0.8. Originál vyšel na webu Mozilla Hacks pod licencí CC-BY-SA. Pod stejnou licencí je k dispozici i zde.

Ukázka

Můžete si prohlédnout funkční ukázku fileUploaderu, nebo si stáhnout JavaScriptový a PHP kód z github.com.

Pozastavení a obnovení uploadu

Základní myšlenka je poskytnout uživateli tlačítko k pozastavení průběhu uploadu a pro jeho následné obnovení. Pozastavení je jednoduché — stačí zavolat metodu abort(). Je nutné se ale ujistit, že to uživatelské rozhraní nenahlásí jako chybu.

Obnovení uploadu je mnohem složitější, protože byl požadavek zrušen a spojení se serverem uzavřeno. Namísto opětovného poslání celého souboru použijeme metodu mozSlice() z objektu Blob, která vrátí zbývající část dat. Poté vytvoříme nové spojení se serverem, tuto část odešleme a připojíme na konec již odeslaných dat.

Rozdělení souboru na části

Odesílaný soubor můžeme rozdělit na části následujícím způsobem:

var chunk = file.mozSlice(start, end);

Stačí nám vědět, kolik bajtů jsme již odeslali na server. To je vše. Mohlo by se zdát, že nejjednodušší způsob je uložit si atribut loaded události ProgressEvent před tím, než upload přerušíme. Toto číslo však nemusí souhlasit s počtem bajtů uložených na serveru. Nejjistější způsob je před samotným obnovením uploadu odeslat dotaz, který nám vrátí velikost přerušeného souboru. Tuto informaci pak můžeme využít při dělení dat pomocí  mozSlice().

Souhrn událostí

(předpokládejme, že upload již běží):

  1. uživatel přeruší upload,
  2. změníme stav UI — přerušeno,
  3. upload je přerušen,
  4. server přeruší zápis na disk.
  5. Uživatel obnoví upload,
  6. změníme stav UI — obnoveno,
  7. dotaz na velikost již zapsaných dat na serveru,
  8. rozdělení souboru na zbývající část,
  9. odesílání,
  10. změníme stav UI — odesílání,
  11. server navazuje na přerušený upload.

JavaScriptový kód

// Předpokládejme, že jsme již odeslali dotaz na velikost
// odeslaného souboru a odpověď xhr.result obsahuje počet
// bajtů souboru, které jsou již zapsané na disk.
var start = xhr.result.numWrittenBytes;
var chunk = file.mozSlice(start, file.size);

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 kód

Jediná změna na serverové části je, že při normálním uploadu vytvoříme nový soubor, zatímco při obnovení na soubor navážeme.

$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 obchází nastavení v php.ini, proto
// musíme omezit velikost souboru sami:

$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;
}

Pozor!

Výše uvedený PHP kód neobsahuje žádné bezpečnostní kontroly, je to pouhý Proof-of-Concept. Uživatel by s takovým kódem mohl na server odeslat libovolný typ souboru, nebo také libovolný soubor přepsat. Je proto nutné před samotným uploadem provést příslušná bezpečnostní opatření – ovšem ta už jsou mimo rámec tohoto článku.

Obnovení uploadu po chybě

Pozastavení a následné navázání uploadu lze použít i při problémech se sítí. Místo abychom znova odesílali celý soubor, navážeme na odesílání dat výše popsaným způsobem.

Poznámka k obnovení uploadu

Jelikož nemáme kontrolu nad tím, jaká data server uložil do souboru před přerušením uploadu, může se stát, že uložíme soubor poškozený. Toto je potřeba ošetřit. Nabízí se metody kontrolního součtu či jiný způsob ověření.

Obnovení uploadu po pádu prohlížeče

Výše popsaný postup se dá dokonce ještě rozšířit. Je možné (alespoň teoreticky) obnovit upload po nečekaném ukončení nebo pádu prohlížeče. Zde ale nastává problém, že po uzavření prohlížeče jsou data odesílaného souboru z paměti smazána. Uživatel tak musí znova vybrat požadovaný soubor před tím, než jej můžeme rozdělit na části a začít znova odesílat.

Také by se dalo použít nové IndexedDB API, pomocí kterého si před vlastním zahájením uploadu soubor uložíme. Po pádu prohlížeče pak stačí neodeslaný soubor načíst z databáze a začít ihned odesílat.

Komentáře

Subscribe
Upozornit na
guest
5 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
karlos

Aneb proc veci resit jednoduse… Stacilo se 10 let zpatky mrknout do Opery…

kkk

Můžete, prosím, vaši myšlenku rozvést?
(Operu používám nějakých 6 let a opravdu nevím, co myslíte.)

Jan Pobořil

Jak ale řešit upload souborů velkých v řádech GB, které se určitě nevejdou do RAM ani databáze v prohlížeči?

Martin Malý

Přimlouval bych se za „jinými prostředky, než je HTTP a formulář na webu“.

georgiksk

Pekný článok. Viac takýchto.

Len podotknem, že funckia slice bola premenovaná aj vo webkit-based prehliadačoch na webkitSlice. Zároveň sa zmenil aj význam parametrov.

Pár poznámok je tu: http://georgik.sinusgear.com/2011/05/06/html5-api-file-slice-changed/

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.