2013-08-20 44 views
4

我有一個rails web應用程序,允許用戶上傳視頻,並將它們存儲在NFS掛載的目錄中。RoR - 在rails中上傳大文件

當前的設置對於較小的文件是適用的,但我還需要支持大文件上傳(最高4GB)。當我嘗試上傳一個4GB的文件時,它最終會發生,但從UX的角度來看是很糟糕的:根據XHR的「進度」事件顯示上傳開始和進度,但在100%之後,仍然等待很長時間(5分鐘)在服務器響應請求之前。

最初我以爲這必須將文件從臨時目錄複製到最終的NFS掛載目錄。但現在我不太確定。在向我的路由添加日誌記錄之後,我發現文件上載進度達到100%之後和我的控制器操作中的代碼運行時(在我將文件移動到NAS之前進行任何處理之前)之間大約需要等待3分鐘, 。

我想知道以下幾點:

  • 什麼是上傳完成後,這家3分鐘的等待過程中,我的動作叫之前發生了什麼?
  • 有沒有辦法讓我解釋在這段時間內正在發生的事情,以便客戶端在上傳完成後立即得到響應,以便它們不超時?
  • 在Rails中如何處理大型文件上傳?這似乎是一個普遍的問題,但我似乎無法找到任何東西。

(注:我最初使用CarrierWave的上傳時,我發現了這個問題,我刪除了,只是處理的文件直接保存使用文件實用程序在我的模型,以確保公正的等待時間不結果一些CarrierWave魔術幕後發生的事情,卻得到了完全相同的結果)

紅寶石-v:1.9.3p362

軌-v:3.2.11

+0

你可以包含你的日誌文件嗎?另外,你在什麼服務器上? – screenmutt

+0

我可以附上日誌,但我不確定這真的會有幫助。在相關時間段內(在上載完成之後,操作執行之前),日誌中絕對沒有任何事情發生。 – Danny

+0

如果它甚至沒有得到你的控制器動作,並且你沒有任何瘋狂的before_filters或around_filters,那麼它必須是你的web服務器或你的中間件。 'rake middleware'中有什麼奇怪的東西? – Taavo

回答

3

我終於找到了我的主要問題的答案: 上傳完成後,在我的操作被調用之前,這3分鐘的等待期間發生了什麼?

在這篇文章中這一切都解釋得很清楚: The Rails Way - Uploading Files

「當瀏覽器上傳文件,它能編碼一種叫做‘多默’格式內容(它是被使用的格式相同當你發送電子郵件附件時)爲了讓你的應用程序對該文件做些什麼,rails必須撤銷這個編碼。要做到這一點,需要讀取巨大的請求體,並將每行與幾個正則表達式進行匹配。這可能是慢得令人難以置信,並使用一個巨大的CPU和內存的容量。」

我試着在帖子中提到的modporter Apache模塊。唯一的問題是,模塊和其相應的插件,分別爲4年前寫的,並且他們的網站不再運行,幾乎沒有任何文檔。

modporter,我想指定我的NFS掛載的目錄作爲PorterDir,希望它可以將文件傳遞給NAS沒有從臨時目錄複製任何額外的內容,但是由於這個模塊似乎忽略了我指定的PorterDir,所以我無法得到這麼多,並且正在返回一個完全不同的路徑給我動作。最重要的是,它返回的路徑根本不存在,所以我不知道我的上傳實際發生了什麼。

我的解決方法

,我得問題迅速解決,所以我現在其中包括以分塊處理文件上傳相應的JavaScript/Ruby代碼寫的有點哈克的解決方案去了。

JS例:

var MAX_CHUNK_SIZE = 20000000; // in bytes 

window.FileUploader = function (opts) { 
    var file = opts.file; 
    var url = opts.url; 
    var current_byte = 0; 
    var success_callback = opts.success; 
    var progress_callback = opts.progress; 
    var percent_complete = 0; 

    this.start = this.resume = function() { 
     paused = false; 
     upload(); 
    }; 

    this.pause = function() { 
     paused = true; 
    }; 

    function upload() { 
     var chunk = file.slice(current_byte, current_byte + MAX_CHUNK_SIZE); 
     var fd = new FormData(); 
     fd.append('chunk', chunk); 
     fd.append('filename', file.name); 
     fd.append('total_size', file.size); 
     fd.append('start_byte', current_byte); 

     $.ajax(url, { 
      type: 'post', 
      data: fd, 
      success: function (data) { 
       current_byte = data.next_byte; 
       upload_id = data.upload_id; 

       if (data.path) { 
        success_callback(data.path); 
       } 
       else { 
        percent_complete= Math.round(current_byte/file.size * 100); 
        if (percent_complete> 100) percent_complete = 100; 
        progress_callback(percent_complete); // update some UI element to provide feedback to user 
        upload(); 
       } 
      } 
     }); 
    } 
}; 

(原諒任何語法錯誤,只是打字這個把我的頭頂部)

服務器端,我創建了一個新的途徑,以接受文件塊。在首次提交塊時,我根據文件名/大小生成upload_id,並確定是否已從中斷的上載中獲得部分文件。如果是這樣,我回傳我需要的下一個起始字節以及id。如果沒有,我存儲第一塊並傳回ID。

該過程使用額外的塊上載附加部分文件,直到文件大小與原始文件大小相匹配。此時,服務器以文件的臨時路徑進行響應。

然後javascript從窗體中刪除文件輸入,並用一個隱藏的輸入替換它,該輸入的值是從服務器返回的文件路徑,然後發佈窗體。然後,最後在服務器端,我處理移動/重命名文件並將其最終路徑保存到我的模型中。

Phew。

+1

作爲一個面向未來讀者的文件,FileUploader僅受Chrome支持,不會被添加到html5規範中。http://www.html5rocks.com/en/tutorials/file/filesystem/ –

3

你可能會考慮使用MiniProfiler獲得更好地瞭解時間花在哪裏。

大文件上傳需要在後臺處理。任何控制器或數據庫訪問都應該簡單地標記該文件已上傳,然後將後臺處理作業排隊以移動它,以及可能需要發生的任何其他操作。

http://mattgrande.com/2009/08/11/delayedjob/

物品在它的精神,每一個實現將是不同的。

+0

我會研究一下MiniProfiler。我確實使用CarrierWave嘗試了Delayed Job,但不幸的是,在我的任何代碼運行之前還有一段時間,甚至在我甚至可以排隊文件副本之前。 – Danny

+0

這裏的想法是,上傳甚至不應該打你的應用程序 - 你配置apache/nginx只接受文件,並把它放在你的地方。您使用javascript將上傳文件的路徑或URL提交給應用程序,然後將作業排隊以實際處理上載。 [s3 direct uploader](https:// github。com/waynehoover/s3_direct_upload)gem在s3上使用了類似的技術,沒有背景。 – Taavo

+0

Woah,除我以外的人鏈接到我的博客。整齊! –