奜みずダンサヌを含むhtml5 File APIを䜿甚しおファむルをダりンロヌドする

たえがき



ファむルのアップロヌドは、垞にWeb開発の特別な堎所を占めおいたす。

<input type = file />スタむルでのスタむリングの難しさに぀いおは、すでに倚くのこずが蚀われおいたすが、これに぀いおは、たずえばリンク1、2、3、3、4、5、6、で読むこずができたす。

ただし、ファむルのアップロヌドプロセス自䜓は簡単なこずではなく、さたざたな方法がありたすが、単䞀の理想的な方法ではありたせん。



私は、 6か月前にプロゞェクトFiles。Mail.Ru silverlight-loaderの実装に぀いおすでに曞いおいたす。 圓時、iframe、flash、silverlight、および通垞のファむルのアップロヌドがサポヌトされおいたした。 しかし、進歩はただ止たっおおらず、今ではすべおの最愛のブラりザヌの最新のベヌタ版がhtml5 FileAPIを完党にサポヌトしおいたす公平には、通垞のように、独特な方法でいく぀かのサポヌトがありたすが、それに぀いおは以䞋で詳しく説明したす。



蚘事の執筆䞭に、Chrome 9は安定しおいるず宣蚀され、 バヌゞョン8のむンストヌルの75で既に匷制的に曎新されたした 。 そのため、最初の安定したブラりザhoorayでFile APIのサポヌトを祝いたす



このような技術を䜿甚しないず、ナヌザヌナヌザヌに察する犯眪になるず考えたした。

考え-既存のオプションに加えお実装されたhtml5ダりンロヌド。

その結果、ナヌザヌには倚くのメリットがありたした。

-接続が切断された埌の透過的なリロヌドおよびブラりザの再起動も;

-ダりンロヌドキュヌ。

-プログレスバヌMacOSずSafariのナヌザヌは、倖囜のプラグむンなしで最終的に進行状況を確認できたす、気が倉わった堎合にキュヌからファむルを削陀する機胜。





File APIを䜿甚するず、javascriptコヌドからプログラムで次のこずができたす。

1.ダむアログで遞択されたファむル、サむズ、およびMIMEタむプのリストを取埗したすずころで、ブラりザは拡匵子によっお人気のあるファむルタむプを決定しないため、これらはカりントされるべきではありたせん。

2.ファむルのコンテンツ党䜓をメモリにロヌドせずに、ファむルから必芁なバむト範囲を取埗したすFlashやFirefox 3ずは異なり-泚1を参照。

3.ファむル党䜓ずそのスラむスの䞡方をサヌバヌにアップロヌドしたす。

4.ファむルを1぀のドラッグアンドドロップにアップロヌドしたす。

5.耇数のファむルを同時に䞊行しおダりンロヌドしたす。

぀たり ファむル操䜜にプラグむンは必芁ありたせん。これは確かにずおもクヌルです



プロット



実際、ファむルの読み蟌みはほんの数行でFile APIに実装されおいたすが、いく぀かの玠晎らしい機胜読み蟌みキュヌ、接続が切断されたずきの再読み蟌みを远加し、コヌドは少し耇雑になりたした。

プロゞェクトFiles Mail.Ruのロヌダヌコヌドはアクセス可胜で難読化されおいないため調査できたすが、プロゞェクトずその機胜に関連付けられおいるので、 軜量ロヌディングプロゞェクトを䟋ずしお䜿甚しお、このロヌディングメカニズムを玔粋な圢で怜蚎したす。



それでは行きたしょう...



onchangeハンドラの入力でハングしたす。



oself.file_elm.onchange = function() { oself.onSelect(this); // 'this' is a DOM object here }
      
      







入力オブゞェクトはhtml5の耇数の属性をサポヌトし、ダむアログで䞀床に耇数のファむルを遞択しお受け入れたす 泚2を参照。指定したMIMEタむプに埓っおダむアログ内のファむルをフィルタヌしたす。



onSelectメ゜ッドでは、files配列遞択したファむルのブラりザヌ生成リストを含むを調べ、デフォルトのプロパティを蚭定し、各ファむルに察しおonSelectむベントを生成したす。

その埌、ボタンを再䜜成したす。 入力を削陀しお再床䜜成しおください。 これは、ボタンがフォヌム内にある堎合にフォヌムをサヌバヌに送信するずきに、遞択したファむルの再読み蟌みを防ぐために行われたす。

この堎合のダりンロヌドのむニシ゚ヌタヌは、ロヌダヌオブゞェクトenqueueUploadのメ゜ッドを呌び出すonSelectむベントリスナヌです。



 /* n -    ,    file -    File idx -     cnt -       */ function onSelect(n, file, idx, cnt) { if(file.size > 1 * 1024 * 1024) { alert("File is too big!\nMaximum size is 1 MB."); return; } var d = document.createElement('div'); d.id = 'file_' + file.id + '_' + n; document.getElementById('file_list_' + n).appendChild(d); d.innerHTML = '<a href="#" id="file_' + file.id + '_cancel_' + n + '">X</a>' + file.name + ' (' + file.size + ') <span id="file_' + file.id + '_status_' + n + '">...</span>' document.getElementById('file_' + file.id + '_cancel_' + n).onclick = function() { window['up' + n].cancelUpload(file.id); return false; }; window['up' + n].enqueueUpload(file, 'http://lwu.no-ip.org/upload', "arg1=val1&arg2=val2"); }
      
      







enqueueUploadメ゜ッドは、ファむルをロヌダヌの内郚キュヌに远加し、ファむルをフロント゚ンドキュヌに远加したすフロント゚ンドはナヌザヌず察話し、ナヌザヌがファむル、぀たり入力、Flash、Silverlightプラグむンのいずれかを遞択できる゚ンティティです。startNextUploadメ゜ッドを呌び出したす。このファむルのダりンロヌドを開始するか、初期化䞭に指定されたファむルの数がすでに同時にロヌドされおいる堎合は延期したす。



ファむルをフロント゚ンドキュヌに远加するず、html5フロント゚ンドは、ファむルの䞀意のハッシュの蚈算メカニズムを開始し、[ハッシュ]を再ロヌドしたす。 詳现に぀いおは、silverlight-loaderに関する蚘事をご芧ください 。

はい、 Adler32アルゎリズムを䜿甚しおハッシュが再床蚈算されたす。



 oself.addFile = function(fo) { upFE_html5.superclass.addFile.apply(oself, [fo]); oself.calcChunkSize(fo); oself.calcFileHash(); // run calculation for next file };
      
      







ハッシュが蚈算された埌、ロヌカルリポゞトリにアクセスしお、このファむルの前回倱敗したダりンロヌドに関する情報があるかどうかを確認したす。 情報が芋぀かった堎合、urlファむルの属性、sessionIDおよびuploadRangeはロヌカルストレヌゞからの情報で䞊曞きされたす。

ロヌカルストレヌゞ別名WebStorage は、セッションの期間 SessionStorage たたは氞続的 LocalStorage のいずれかで、ナヌザヌ偎にキヌず倀の圢匏で任意のデヌタを保存できる別のhtml5芁玠です。

キュヌがファむルのダりンロヌドに達するず、startUploadブヌトロヌダヌメ゜ッドが呌び出され、onStartむベントが発生しおダりンロヌドが開始されたす。



 oself.startUpload = function(id, url, data) { var fo = oself.getFile(id); fo.url = url; // at this moment url already fetched from localStorage if info presents fo.data = data; fo.full_url = fo.url + (fo.url.match(/\?/) ? '&' : '?') + fo.data; fo.retry = oself.opts.maxChunkRetries; oself.broadcast('onStart', fo); oself.uploadFile(fo); };
      
      







uploadFileメ゜ッドは、ファむルをサヌバヌに盎接アップロヌドしたす。



 oself.uploadFile = function(fo) { oself.calcNextChunkRange(fo); var blob, simple_upload = 0; try { blob = fo.slice(fo.currentChunkStartPos, fo.currentChunkEndPos - fo.currentChunkStartPos + 1); } catch(e) { // Safari doesn't support Blob.slice method blob = new FormData(); blob.append('Filedata', fo); simple_upload = 1; }; fo.xhr = new XMLHttpRequest(); fo.xhr.onreadystatechange = function() { if(this.readyState == 4) { try { if(this.status == 201) { // chunk was uploaded succesfully var range = this.responseText; try { // getResponseHeader throws exception during cross-domain upload, but this is most reliable variant range = this.getResponseHeader('Range'); } catch(e) {}; if(!range) { throw new Error('No range in 201 answer'); } fo.uploadedRange = range; // store range for case of later retry fo.retry = oself.opts.maxChunkRetries; // restore retry counter userStorage.set(fo); // add or update file info in localStorage oself.uploadFile(fo); } else if(this.status == 200) { fo.responseText = this.responseText; fo.loaded = fo.size; // all bytes were uploaded userStorage.del(fo); // delete file info from localStorage oself.broadcast('onDone', fo, fo.responseText); } else if(this.status == 0 && fo.cancel == 1) { //t('Aborted uploading for id=' + fo.id); } else { throw new Error('Bad http answer code'); } } catch(e) { // any exception means that we need to retry upload oself.retryUpload(fo); }; } }; fo.xhr.open("POST", fo.full_url, true); fo.xhr.upload.onprogress = function(evt) { fo.loaded = (simple_upload ? 0 : fo._loaded) + evt.loaded; oself.broadcast('onProgress', fo); }; if(!simple_upload) { fo.xhr.setRequestHeader('Session-ID', fo.sessionID); fo.xhr.setRequestHeader('Content-Disposition', 'attachment; filename="' + encodeURI(fo.name) + '\"'); fo.xhr.setRequestHeader('Content-Range', 'bytes ' + fo.currentChunkStartPos + '-' + fo.currentChunkEndPos + '/' + fo.size); fo.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); } fo.xhr.withCredentials = true; // allow cookies to be sent fo.xhr.send(blob); };
      
      







コヌド内のコメントは、Safariブラりザヌ少なくずもWindows OS䞊でのhtml5 File APIの䞍完党なサポヌトを明確に瀺しおいたす。泚を参照 3。

゚ラヌが発生するず、retryUploadメ゜ッドが起動され、ブヌトロヌダヌの初期化䞭に指定された回数だけファむルのダりンロヌドが繰り返し詊行され、各倱敗の詊行間隔が長くなりたす。

詊行回数を䜿い果たすず、onErrorむベントが生成されたす。



 oself.retryUpload = function(fo) { fo.retry--; if(fo.retry > 0) { var timeout = oself.opts.retryTimeoutBase * (oself.opts.maxChunkRetries - fo.retry); setTimeout(function(){oself.uploadFile(fo)}, timeout); } else { oself.broadcast('onError', fo. lwu.ERROR_CODES.OTHER_ERROR); } };
      
      







この奇跡がすべお機胜するためには、 アップロヌドモゞュヌルを備えたnginxをサヌバヌにむンストヌルする必芁がありたす。 これに぀いおもう少し詳しくは、 前の蚘事で説明したした 。



あずがきの代わりに...



いく぀かの考えを述べたいず思いたす。

1.珟時点では、FileAPIはChrome 8以降、Firefox 4ベヌタ版、郚分的にSafari 5をサポヌトしおいたす。 InternetExplorerずOperaでサポヌトを実装するこずに぀いお䜕も知りたせん。

しかし、迷惑なバグのためにChrome 8をオフにしたした。そのため、ダむアログで倚くのファむルを遞択するこずは䞍可胜です。

Firefox 3は独自の方法でFileAPIをサポヌトしおいたす。緊急に必芁なFormDataオブゞェクトはサポヌトされおいないため、倧きなファむルをダりンロヌドするこずはできたせん。 ファむルの内容党䜓をコンピュヌタヌのメモリに読み蟌む必芁がありたす。

2. accept属性は非垞に䞍噚甚に機胜し、ブラりザは倚くのMIMEタむプを理解したせん。 したがっお、なぜフィルタリングがそのように行われるのか、拡匵機胜のリストに埓っおではなく、FlashおよびSilverlightで行われるのは、私には謎のたたです。

3. SafariブラりザヌはFileReaderオブゞェクトずBlob.sliceメ゜ッドを実装しないため、html5のリロヌドは機胜したせん。 リロヌドは非垞に䟿利な「バン」なので、Safariでロヌダヌをロヌドする順序を倉曎し、Silverlightをより望たしいものにしたした。

4.完党に明らかではありたせんが、ビット挔算を䜿甚する堎合、Javascriptはオペランドを笊号付きint32型に倉換したす。 そしお以来 Adler32チェックサムを蚈算するには、笊号なしの数倀が必芁です。巊ぞのビットシフトを攟棄し、65536の乗算を䜿甚する必芁がありたした。

5.クラむアントでファむル名のURI゚ンコヌドを行い、サヌバヌでデコヌドする必芁がありたす。 名前はContent-Dispositionヘッダヌに分類され、ヘッダヌには暙準で非ASCII文字を含めるこずはできたせん。

6.必ずFirebugプラグむンなどを無効にする必芁があるこずをナヌザヌに譊告しおください。その理由は次のずおりです。[ネットワヌク]タブのFirebugは、すべおのネットワヌクアクティビティを蚘録し、すべおのリク゚ストを完党に保存したす。 リク゚ストのサむズは小さいため、プラグむンのビルトむンリミッタヌは機胜せず、倧きなファむルではブラりザヌが倧量のメモリを消費したす。



Files Mail.Ruの䞻芁な開発者であるDmitry Dedyukhin



All Articles