好みとダンサーを含む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

Source: https://habr.com/ru/post/J113508/


All Articles