大きなファイルをダウンロードするか、安価な共有ホスティングの制限を回避する方法

ここでも、比較的大きなファイルをダウンロードするという問題が発生しました。 具体的には、クライアントは管理パネルから20〜40メガバイトの動画をサイトにアップロードしたいと考えていました。 私たちの賢明な時代には、同じような大きさはそのような些細なことであり、それについて話すのは残念です。 しかし、突然、すべてが共有ホスティングの設定に反しました。 アップロードされたファイルの最大サイズが2Mであり、この数値を変更する方法がないことを知り、私たちはぞっとしました。 そして、いくつかの理由でホスティングを変更することはできません-少なくとも今は。

私たちは、悲惨な共有ホスティングの制限を回避するという課題に直面しています。 このようなバイパスの原理は明らかです。ファイルを断片に分割し、部品で埋め、サーバー側でまとめる必要があります。 しかし、これは手動で行うべきではありません-ユーザーはファイルを選択し、「送信」ボタンをクリックする必要があります。 これを行う方法?


最初の反応は、さまざまなFlashアップローダーの機能を調べることです。 結局のところ、世界の技術的思考が、ファイルを部分的にダウンロードするような有用なことを実現していないということはありえません。 Uploadify、SWFUpload、FancyUpload、jqUploader、jquery-transmitをループします。 しかし、すべて無駄です。 目的の機能が表示されません。 さらに掘り下げる必要がある可能性が高いですが、時間が不足しており、すでに何かをしなければなりません...

上記は悲しいです。 ただし、これが管理パネルであるという事実は、私たちの手にかかっています。 つまり ブラウザ間の互換性にまったく焦点を当てる必要はありません。 このメカニズムがクライアントのブラウザで動作することで十分です。これは(奇跡です!)FFです。

FFでは、最新バージョンでは、ファイルアップロードフィールドにロードされたファイルの内容を文字列に変換する機会があることをすぐに思い出します。 そして、この行を細かく分けて、Ajaxを使用して部分的にアップロードしたいという欲求になります。

クライアント部


まず、必要なものを静的HTMLで描画します。

 <form> <input type="file" id="myfile"> </form> <a href="#" onClick="big_file_upload($('#myfile'))"></a> 

つまり リンクをクリックすると、big_file_upload関数を呼び出す必要があります。この関数には、ファイルのコンテンツを取得するオブジェクトが転送されます。 $( '#myfile')の構築に注意してください。 サーバーにファイルを転送するときにajaxリクエストにも使用するjQueryライブラリーを接続する必要性について詳しく説明する必要はないと思います。

次に、同じbig_file_upload関数を記述する必要があります。

 var upload_chunk_size = 120000;
 //この変数には、ファイルを分割するピースのサイズが含まれます。
 
関数big_file_upload(ファイル){
   // .....
 }

ファイルの内容を取得する

ファイルの内容を取得するには、次の構成を使用します。

   var data = file.get(0).files.item(0).getAsDataURL();

その意味を説明します。

同様の構成で、ファイル名を取得します。

   var filename = file.get(0).files.item(0).fileName;

コンテンツはData:URL形式であるため、MIMEタイプとエンコード方式に関する情報を含むヘッダー部分を切り捨てることをお勧めします。 関数のより一般的なバージョンでは、この情報を使用する必要がありますが、この例では、デコード時にのみ干渉します。 したがって、ヘッダーを区切る最初のコンマ(すべてを含む)ですべてを単純にカットします。

   varカンマ= data.indexOf( '、');
   if(comma> 0)data = data.substring(comma + 1);

ファイルをチャンクで送信する

ここではすべてがささいなものです。

  var pos = 0;
 
  while(pos <data.length){
 
     $ .post( '/ upload.php'、{
       filename:ファイル名、
      チャンク:data.substring(pos、pos + upload_chunk_size)
     });
 
     pos + = upload_chunk_size;
   }

サーバー側


ここで、サーバーにPHPスクリプトupload.phpを受け入れさせます。 バリアントの例では、非常に簡単です。

   $ filename = $ _POST ['filename'];
   $ f = fopen( "/ dir / to / save / $ filename"、 "a");
   fputs($ f、base64_decode($ _ POST ['chunk']));
   fclose($ f);

ファイルはオプション「a」で開きます。 既存のファイルを上書きするのではなく、補足することをお勧めします。 このようにして、ファイル全体を断片から組み立てます。

このスクリプトはサイトの非公開の管理者部分に配置する必要があることを誰もが理解していると思うので、さまざまな人格がアクセスすることはできません。 さらに、ファイル名とファイル自体の有効性を確認する必要があります。 そうでなければ、それは脆弱性でさえありません、しかし...私はそのような言葉さえ知りません...

実行してみてください


試しましたか? うまくいきましたか? 私はそれが予想されたものとはまったく違っていたと確信しています。 ファイルがダウンロードされたようです。 長ささえ正しいようです。 しかし、内容はある種の混乱です。

なぜこれが起こったのですか? 答えは簡単です。POST要求は一度に複数の部分で非同期モードで送信されるため、送信コマンドが送信された正確な順序でサーバーに送信されることを誰も保証しません。 それだけでなく、誰も保証しませんが、私は彼らが正しい順序で来ることは決してないことを直接確認します。 いつもおridgeになります。

したがって、どんなに悲しくても、非同期を無効にする必要があります。 ファイルを送信する前に、次を実行します。

  $ .ajaxSetup({async:false});

これですべてが正常になりました。 ただし、ファイルは目的の順序で収集されますが、他のスクリプトのダウンロードは一時停止されます。 したがって、ユーザーに迷惑をかけないようにするには、パーセンテージまたは進行状況バーをどこかに表示するとよいでしょう。 したがって、実行可能なスクリプトは次のようになります。

 var upload_chunk_size = 120000;  //チャンクサイズ
 
関数big_file_upload(ファイル){
   var data = file.get(0).files.item(0).getAsDataURL();  //ファイルの内容を取得します
   var filename = file.get(0).files.item(0).fileName;  //ファイル名を取得します
 
   varカンマ= data.indexOf( '、'); 
   if(comma> 0)data = data.substring(comma + 1);  //ヘッダーデータを切り取ります:: URL
 
   var pos = 0;
   $ .ajaxSetup({async:false});  //非同期をオフにします
 
   while(pos <data.length){
 
     $ .post( '/ upload.php'、{// Send POST
       filename:filename、//ファイル名
      チャンク:data.substring(pos、pos + upload_chunk_size)//ファイルの一部
     });
 
     pos + = upload_chunk_size;
 
     var p = Math.round(pos * 100 / data.length);  //送信した割合を計算します
     $( '#progress')。text(p + '%');  //ユーザーの安心のためにデジタルを描画します
   }
 }

短所


それらなしで...

  1. この例では、getAsDataURLメソッドが常にbase64エンコードデータを返すことを前提としています。 実際、常にそうなるとは思わない。 良い方法では、ヘッダーを捨てるのではなく、サーバー側に転送する必要があります。サーバー側は、さまざまな方法でエンコードされたデータを処理するように教えられます。
  2. 2回送信されたファイルは、サーバーに2回記録されます。 そしてそれはそれ自身を補完します。 これを回避するには、明らかに、名前に加えて、ダウンロード用の他の一意の識別子を渡す必要があります。 しかし、これは、一般的に言えば、ファイル名を生成および送信する方法の問題です。 ここに普遍的なレシピはありません。
  3. クライアントスクリプトは(ファイルサイズとチャネルの厚さに応じて)長時間実行され、FFが尋ねることさえあるかもしれません。
  4. ブラウザー間の互換性。 悲しいかな、ファイルの内容の取得はFFでのみ機能します。 3.0、3.5、および3.6でテスト済み。 以前は、手元にそれらがないことは確認されていませんでした。 FF開発者自身が代わりにFileAPIを使用する方法を推奨していますが、3.6でしか登場しませんでした。
  5. 本当に大きなファイル(数百メガバイト、ギガバイト)はこの方法ではダウンロードできません。 制限は、ブラウザで使用可能なメモリの量によって異なります。


どうする?


おそらく、非同期モードでも同じことを試してください。 各ピースについて、ファイル内のその場所に関する情報も送信します。 同時に、サーバー側は非常に複雑になります。

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


All Articles