2017-09-07 27 views
0

をアップロード私はいくつかの困難に遭遇しました。Railsは大容量のファイル

使用しているサーバーは:pumaです。

ファイル転送は速度です(ローカルネットワーク(ギガビット)ではajax xhrの進行が続きます)。

ただし、ファイル/ tmp/RackMultipart *の保存には時間がかかりました。

ファイルがRackによってメモリにロードされ、そのプロセスが/ tmp /に保存されているとします。その後、コントローラは処理を進める。

コードは画像のような小さなファイルでも機能します。

しかし、実行完了は約1分ほどかかる大きなファイル> 100 Moのため

...

マイコード:

アップロードエリア: ビュー/ _attachments.html.erb

<div class="card"> 

    <div class="card-header"> 
     Fichiers 
    </div> 

    <div class="card-block"> 
     <span id="attachment-area-message"></span> 
     <div id="attachment-area"> 
      Déposez vos fichiers ici 
     </div> 
     <!-- Area for progress bar --> 
     <div id="progress-wrapper"></div> 
     <script> 
      var attachment_token = '<%= form_authenticity_token %>'; 
      var attachment_model_name = '<%= fileable.class.name %>'; 
      var attachment_model_id = '<%= fileable.id %>'; 
     </script> 
    </div> 

    <div class="card-block"> 
     <div class="attachfiles-wrapper"> 
      <div id="attachfiles"> 
       <% fileable.attachments.includes('user').order(created_at: :asc).each do |attachment| %> 
         <%= render partial: 'app/attachments/attachment', locals: { attachment: attachment } %> 
       <% end %> 
      </div> 
     </div> 
    </div> 
</div> 

アップロードを開始するJSファイル:

$(document).on('turbolinks:load', function() { 

    new Clipboard('.btn-clipboard'); 

    var upload_mime = [ 
     'application/zip', 
     // Image 
     'image/png', 
     'image/jpeg', 
     'image/gif', 
     'image/tiff', 
     'image/svg+xml', 
    ]; 
    var upload_maxSize = 3000000000; 
    var server_url = '/app/attachments/upload.js'; // Route for upload file, .js for the js call back 

    var element = $("#attachment-area"); 

    //  EVENTS 
    // ---------------------------------------------------------------------------- 
    element.on('dragover', function(e) { 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('dragenter', function(e) { 
     element.addClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('dragleave', function(e) { 
     element.removeClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('drop', function(e) { 
     element.removeClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
     if (e.originalEvent.dataTransfer){ 
      if (e.originalEvent.dataTransfer.files.length > 0) { 
       console.log(e.originalEvent.dataTransfer.files); 
       upload(e.originalEvent.dataTransfer.files); 
      } 
     } 
     return false; 
    }); 


    //  UPLOADS 
    // ---------------------------------------------------------------------------- 
    var upload = function(files) { 

     // Send each file 
     $.each(files, function(key, file) { 

      // TEST THE FILE 
      // ---------------------- 
      var FileValidate = true; 

      // Size 
      if(file.size > upload_maxSize) { 
       $('#attachment-area-message').append(file.name + " : Fichier trop lourd (3 Go maximum) : " + file.size); 
       FileValidate = false; 
      } 

      // Mime type 
      if(upload_mime.indexOf(file.type) == -1) { 
       $('#attachment-area-message').append(file.name + " : Type de fichier non authoris&eacute; : " + file.type); 
       $('#attachment-area-message').append("<br>Essayez de zipper le fichier"); 
       FileValidate = false; 
      } 

      if(!FileValidate) return true; // Continue to next iteration 

      // SEND FILE 
      // ---------------------- 
      console.log(file); 

      var formData = new FormData(); 
      formData.append('attachment[file]', file); 
      formData.append("attachment[model_name]", attachment_model_name); 
      formData.append("attachment[model_id]", attachment_model_id); 

      console.log(formData); 

      // Progress Bar Name 
      var progress_name = file.name.replace(/[^a-zA-Z]/g,'-').toLowerCase(); 

      // Send the request :) 
      $.ajax({ 
       url: server_url, 
       data: formData, 
       type: 'POST', 
       beforeSend: function(request) { 
        request.setRequestHeader('X-CSRF-Token', attachment_token); 
        console.log('BEFORE SEND'); 
       }, 
       contentType: false, // NEEDED, DON'T OMIT THIS (requires jQuery 1.6+) 
       processData: false, // NEEDED, DON'T OMIT THIS 

       xhr: function() { 

        // create an XMLHttpRequest 
        var xhr = new XMLHttpRequest(); 
        console.log('xhr'); 

        xhr.upload.onprogress = function (e) { 
         console.log('xhr progress'); 
         if (e.lengthComputable) { 
          var percente = Math.round((e.loaded * 100)/e.total); 
          $('.' + progress_name + ' .progress-bar').width(percente + "%"); 
         } 
        }; 

        xhr.onloadstart = function (e) { 
         console.log('xhr onloadstart'); 
         $('#progress-wrapper').append('<div class="' + progress_name + '" style="margin-top:5px;">' 
         + '<span class="description">' + file.name + '</span>' 
         + '<div class="progress" id="file-upload-bar">' 
         + '<div class="progress-bar bg-info" role="progressbar" style="width:0%; height:10px;" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>' 
         + '</div></div>'); 

        }; 

        xhr.onload = function (e) { 
         console.log('xhr onload'); 
         if (xhr.status === 200) eval(xhr.responseText); // Grab the return of rails controller (format.js) 
        }; 

        xhr.onloadend = function (e) { 
         console.log('xhr onloadend'); 
         $('.' + progress_name).remove(); 
        }; 

        return xhr; 

       } 
      }); 

     }); 

    }; 
}); 

コントローラ:

(アタッチメントモデルは、ファイル可能な多形ベースです)。

class App::AttachmentsController < AppController 

    before_action :find_fileable 

    def upload 

     # Get the File 
     uploaded_io = attach_params[:file] 

     logger.debug '---------' 
     logger.debug params.inspect 
     logger.debug '---------' 

     # Define file destination 
     dest = Rails.root.join('public', 'uploads', 'attachments', attach_params[:model_name], attach_params[:model_id], uploaded_io.original_filename) 
     file_name = uploaded_io.original_filename 
     file_basename = File.basename(uploaded_io.original_filename, '.*') 
     file_extname = File.extname(uploaded_io.original_filename) 

     # Make dir 
     dir = File.dirname(dest) 
     FileUtils.mkdir_p(dir) unless File.directory?(dir) 

     # Test if file exist (and update version if needed) 
     if File.exist?(dest) 
      version = 0 
      loop do 
       version += 1 
       file_name = file_basename + '-' + version.to_s + file_extname 
       dest = Rails.root.join('public', 'uploads', 'attachments', attach_params[:model_name], attach_params[:model_id], file_name) 
       break if !File.exist?(dest) 
      end 
     end 

     # Copy file to dest 
     #FileUtils.cp uploaded_io.path, dest 
     File.open(dest, 'wb') do |file| 
      file.write(uploaded_io.read) 
     end 

     # Save in database 
     @attach = @fileable.attachments.new 
     @attach.user_id = @current_user.id 
     @attach.name = file_name 
     @attach.size = uploaded_io.size 
     @attach.mime = uploaded_io.content_type 
     @attach.key = Digest::SHA1.hexdigest([Time.now, rand].join) 

     respond_to do |format| 
      if @attach.save 
       flash[:success] = "Fichier ajouté" 
       format.js # upload.js callback add new file to the list of files 

      else 
       flash[:warning] = "Fichier non enregistré :(" 
      end 
     end 
    end 

    private 

     def attach_params 
      params.require(:attachment).permit(:model_id, :model_name, :file) 
     end 

     def find_fileable 
      @fileable = Task.find_by_id(attach_params[:model_id]) if attach_params[:model_name] == 'Task' 
     end 

end 

私は別のファイル管理ソリューションをテストした:CarrierWave、神社、...

は、残念ながら、問題がまだそこにあります。常にラックは前方に保管してください。 ヘルプや考え方はありますか?私はこの "ラック"を食べたい

ありがとう、 Seb。

+0

が問題ですアップロードに時間がかかりすぎる、または完了しない –

+0

こんにちはアロン。数分後にアップロードが完了します。問題は時間です:) – Oncleroger

答えて

0

問題はラックマルチパートパーサーがディスクに書き込むことです(速い部分ですが)実装が遅く、高価です(rack/rack#1075参照)。ただし、ラックのマスターはマルチパートパーサーのパフォーマンスが大幅に向上しているため、マスターを使用することで問題は解決します。

gem "rack", github: "rack/rack" 

私たちは、次のスクリプトを実行して、これを確認することができます

require "rack" 
require "rack/test_app" # https://github.com/kwatch/rack-test_app 
require "benchmark" 

app = -> (env) do 
    puts Benchmark.realtime { Rack::Request.new(env).params } # trigger multipart parsing 
    [200, {}, []] 
end 

File.write("file.txt", "a" * 100*1024*1024) 

test_app = Rack::TestApp.wrap(app) 
test_app.post("/", multipart: {file: File.open("file.txt")}) 

ラック2.0.3:

$ ruby multipart.rb 
62.617582999984734 

をラックマスター:

$ ruby multipart.rb 
0.3564810000243597 
+0

こんにちはジャンコ、あなたの答えをありがとう。私はactivestorage(5.2版用の新しい宝石...)を試そうとしていました。私は今ラックマスターをテストします。 – Oncleroger

+0

ありがとうございました! 130 Mo前後のファイルの場合、古いラックのバージョン:1分40 、ラックマスター:6秒。あなたは私を助けてくれる – Oncleroger

0

私はチャンク方式でテストしています。私のファイルの1MOの一部を作成し、バイナリで送信します。完璧ではありませんが、より良いです。 この方法では、レールはtmpにMultiRack *ファイルを作成しませんが、サーバーとクライアントの両方のメモリを使用します。

ジャバスクリプトファイル:

$(document).on('turbolinks:load', function() { 

    new Clipboard('.btn-clipboard'); 

    var upload_url = '/app/attachments/upload'; // Route for upload file, .js for the js call back 
    var upload_part_url = '/app/attachments/upload/part/'; 
    var upload_mime = [ 
     'application/zip', 

     // Vidéo 
     'video/mp4', 
     'video/mpeg', 
     'video/x-flv', 

     // Audio 
     'audio/mpeg', 

     // Image 
     'image/png', 
     'image/jpeg', 
     'image/gif', 
     'image/tiff', 
     'image/svg+xml', 

     // Text 
     'text/csv', 
     'text/html', 

     // Application 
     'application/pdf', 
     'application/msword', 
     'application/excel', 
     'application/mspowerpoint', 

     // Adobe 
     'application/vnd.adobe.indesign', 
     'application/x-indesign', 
     'application/indesign', 

     'image/vnd.adobe.photoshop', 
     'application/x-photoshop', 
     'application/photoshop', 
     'application/psd', 
     'image/psd', 

     'application/illustrator', 
     'application/postscript' 
    ]; 
    var upload_maxSize = 3000000000; 

    //  EVENTS on DROP AREA 
    // ---------------------------------------------------------------------------- 
    var element = $("#attachment-area"); // Drop area 
    element.on('dragover', function(e) { 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('dragenter', function(e) { 
     element.addClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('dragleave', function(e) { 
     element.removeClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
    }); 
    element.on('drop', function(e) { 
     element.removeClass('active'); 
     e.preventDefault(); 
     e.stopPropagation(); 
     if (e.originalEvent.dataTransfer){ 
      if (e.originalEvent.dataTransfer.files.length > 0) { 

       // We upload the files 
       $.each(e.originalEvent.dataTransfer.files, function(key, file) { 

        // Test the file 
        var FileValidate = true; 

        // Size 
        if(file.size > upload_maxSize) { 
         $('#attachment-area-message').append(file.name + " : Fichier trop lourd (3 Go maximum) : " + file.size); 
         FileValidate = false; 
        } 

        // Mime type 
        if(upload_mime.indexOf(file.type) == -1) { 
         $('#attachment-area-message').append(file.name + " : Type de fichier non authoris&eacute; : " + file.type); 
         $('#attachment-area-message').append("<br>Essayez de zipper le fichier"); 
         FileValidate = false; 
        } 

        // Begin the upload 
        if(FileValidate) upload(file); 
       }); 
      } 
     } 
     return false; 
    }); 

    //  UPLOAD 
    // ---------------------------------------------------------------------------- 
    var upload = function(file) { 

     console.log(file); 

     var formData = new FormData(); 
     formData.append("attachment[model_name]", attachment_model_name); 
     formData.append("attachment[model_id]", attachment_model_id); 
     formData.append('attachment[file_name]', file.name); 
     formData.append('attachment[file_size]', file.size); 
     formData.append('attachment[file_mime]', file.type); 

     // Progress Bar Name 
     // var progress_name = file.name.replace(/[^a-zA-Z]/g,'-').toLowerCase();   

     // Send the file infos 
     var req = new XMLHttpRequest(); 

     // Request events 
     req.upload.onprogress = function (e) { 
      console.log('xhr progress'); 
     }; 

     req.onloadstart = function (e) { 
      console.log('xhr onloadstart'); 
     }; 

     // Error 
     req.onerror = function (e) { 

     } 

     // Success 
     req.onload = function (e) { 
      console.log('xhr onload'); 
      if (req.status === 200) { 
       attach = JSON.parse(req.responseText); 
       if(typeof attach.id !== 'undefined') uploadFileData(file, attach.id); // Send the data 
      } 
     }; 

     // Complete 
     req.onloadend = function (e) { 
      console.log('xhr onloadend'); 
     }; 

     // Send the file infos Request 
     req.open("POST", upload_url); 
     req.setRequestHeader('X-CSRF-Token', attachment_token); 
     req.send(formData); 

    }; 


    //  UPLOAD FILE CHUNKS 
    // ---------------------------------------------------------------------------- 
    var uploadFileData = function(file, id) { 

     var reader = new FileReader(); 

     // Process after the file is read 
     reader.onload = function (e) { 

      var chunkSize = 1*1024*1024; 
      var buffer = this.result; 
      var fileSize = buffer.byteLength; 
      var segments = Math.ceil(fileSize/chunkSize); 
      var count = 0; 
      var fileId = id; 

      // Send part 
      (function sendPart() { 

       var segSize = Math.min(chunkSize, fileSize - count * chunkSize); 
       var returnFormat = segSize < chunkSize ? '.js' : '.json' ; 

       if (segSize > 0) { 

        var chunk = new Uint8Array(buffer, count++ * chunkSize, segSize); // get a chunk 

        // update progress bar 

        var req = new XMLHttpRequest(); 

        // Request events 
        req.upload.onprogress = function (e) { 
         console.log('part progress : ' + count); 
        }; 
        req.onloadstart = function (e) { 
         console.log('part onloadstart : ' + count); 
        }; 
        // Error 
        req.onerror = function (e) { 

        } 

        // Success 
        req.onload = function (e) { 
         console.log('part next : ' + count); 
         sendPart(); // Success -> Next part 
        }; 

        // Send the file part data 

        req.open("POST", upload_part_url + fileId + returnFormat); 
        req.setRequestHeader('X-CSRF-Token', attachment_token); 
        req.setRequestHeader('Content-Type', 'application/octet-stream'); 
        req.send(chunk); 

       } 
       else { 
        // hide progress bar 
        console.log("part Done : " + count);    
       } 

      })() 
     }; 

     // Read the file 
     reader.readAsArrayBuffer(file); 

     reader.onprogress = function(e) { 
      // loaded += e.loaded; 
      // progress.value = (loaded/total) * 100; 
     }; 

    } 
}); 

コントローラ:

class App::AttachmentsController < AppController 

    before_action :find_fileable, only: [:upload] 

    def upload 

     # Define file destination 
     model_name = attach_params[:model_name] 
     model_id = attach_params[:model_id] 
     file_name = attach_params[:file_name] 
     file_basename = File.basename(file_name, '.*') 
     file_extname = File.extname(file_name) 
     dest = Rails.root.join('public', 'uploads', 'attachments', model_name, model_id, file_name) 

     # Make dir 
     dir = File.dirname(dest) 
     FileUtils.mkdir_p(dir) unless File.directory?(dir) 

     # Test if file exist (and update version in name if needed) 
     if File.exist?(dest) 
      version = 0 
      loop do 
       version += 1 
       file_name = file_basename + '-' + version.to_s + file_extname 
       dest = Rails.root.join('public', 'uploads', 'attachments', model_name, model_id, file_name) 
       break if !File.exist?(dest) 
      end 
     end 

     # Save in database 
     @a = @fileable.attachments.new 
     @a.user_id = @current_user.id 
     @a.name = file_name 
     @a.size = attach_params[:file_size] 
     @a.mime = attach_params[:file_mime] 
     @a.key = Digest::SHA1.hexdigest([Time.now, rand].join) 
     @a.completed = false 

     @a.save 

     logger.debug '----' 
     logger.debug @a.to_json 
     logger.debug '----' 

     render status: 200, json: @a.to_json 

    end 


    def upload_part 

     @attach = Attachment.find(params[:id]) 

     logger.debug '----' 
     logger.debug @attach.inspect 
     logger.debug '----' 

     dest = @attach.path 

     # Copy file to dest 
     File.open(dest, 'ab') do |file| 
      file.write(request.raw_post) 
     end 

     logger.debug '----' 
     logger.debug File.size(dest) 
     logger.debug @attach.size 
     logger.debug '----' 

     respond_to do |format| 
      format.js 
      format.json { render status: 200, json: { "status": "yop"} } 
     end 
    end 

    private 

     def attach_params 
      params.require(:attachment).permit(:model_name, :model_id, :file_name, :file_size, :file_mime) 
     end 

     def find_fileable 
      @fileable = Task.find_by_id(attach_params[:model_id]) if attach_params[:model_name] == 'Task' 
     end 

end 

は、次のエピソードであなたを参照してください。私は検索を続けます...

関連する問題