Как стать автором
Обновить

Индикатор загрузки файлов в веб-приложениях на Ruby on Rails

Ruby *
Задача: показать как реализовать индикатор загрузки файла при различных конфигурациях Ruby on Rails:
Я разделю цикл статей на три части.
mongrel
mongrel(s)+nginx
mod_rails

Нижеописанное проверено с:
Ruby on Rails 2.2.2
ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
mongrel 1.1.5
Mac OS X 10.5.6


Часть I. Как это сделать с mongrel'ом


В основе легла статья, линк на которую есть в самом конце. Но повторив ее у меня на новых рельсах и многрелах ничего не получилось. Я немножно ее изменил.

Создание каркаса:
rails upload
cd upload
rm public/index.html
script/generate controller home index upload progress
touch app/views/layouts/application.html.erb


Добавление роутов:
Copy Source | Copy HTML
  1. ActionController::Routing::Routes.draw do |map|
  2.   map.root :controller => 'home'
  3.   map.upload '/upload', :controller => 'home', :action => 'upload'
  4.   map.progress '/progress', :controller => 'home', :action => 'progress'
  5. end
  6.  


Этап подготовки завершен. Теперь надо немного пояснить каким образом фунционирует индикатор загрузки файлов. Сабмит формы должен идти в iframe, а с самой страницы помощью ajax запросов можно с нужной частотой обращаться к определенному контроллеру, который будет возвращать текущее состояние (можно в json, можно html, здесь сделано javascript-ом), и в зависимости от них на странице двигать индикатор.

Итак, контроллер один, а в нем три метода: index для вывода формы для загрузки, upload — обработка загруженного файла, progress — для получения данных о статусе загрузки. Контроллер выглядит следующим образом:

Copy Source | Copy HTML
  1. class HomeController < ApplicationController
  2.   skip_before_filter :verify_authenticity_token, :only => [:progress]
  3.  
  4.   def index
  5.     @upid = Time.now.tv_sec.to_s
  6.   end
  7.  
  8.   def upload
  9.     render :text => 'done'
  10.   end
  11.  
  12.   def progress
  13.     @status = Mongrel::Uploads.check(params[:upload_id])
  14.     respond_to do |format|
  15.       format.js
  16.     end
  17.   end
  18. end


skip_before_filter необходим так как запросы будет идти без токена. @upid — некий уникальный идентификатор.
Теперь необходимо написать некий плагин для монгрела, который будет перехватывать аплоад файлов (а точнее обращение к конкретному url) и хранить данные о состоянии загрузки, кроме того к этим данным нужно как-то обращаться. Хорошо, что это уже реализовано и код можно взять из svn:

svn co svn://rubyforge.org/var/svn/mongrel/trunk/projects/mongrel_upload_progress

Я предлагаю сделать следующим образом: создать файл lib/progress_plugin.rb со следующим содержимым:
Copy Source | Copy HTML
  1. class Upload < GemPlugin::Plugin "/handlers"
  2.   include Mongrel::HttpHandlerPlugin
  3.  
  4.   def initialize(options = {})
  5.     @path_info = Array(options[:path_info])
  6.     @frequency = options[:frequency] || 3
  7.     @request_notify = true
  8.     if options[:drb]
  9.       require 'drb'
  10.       DRb.start_service
  11.       Mongrel.const_set :Uploads, DRbObject.new(nil, options[:drb])
  12.     else
  13.       Mongrel.const_set :Uploads, Mongrel::UploadProgress.new
  14.     end
  15.     Mongrel::Uploads.debug = true if options[:debug]
  16.   end
  17.  
  18.   def request_begins(params)
  19.     upload_notify(:add, params, params[Mongrel::Const::CONTENT_LENGTH].to_i)
  20.   end
  21.  
  22.   def request_progress(params, clen, total)
  23.     upload_notify(:mark, params, clen)
  24.   end
  25.  
  26.   def process(request, response)
  27.     upload_notify(:finish, request.params)
  28.   end
  29.  
  30.   private
  31.     def upload_notify(action, params, *args)
  32.       return unless @path_info.include?(params['PATH_INFO']) &&
  33.         params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
  34.         upload_id = Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
  35.       if action == :mark
  36.         last_checked_time = Mongrel::Uploads.last_checked(upload_id)
  37.         return unless last_checked_time && Time.now - last_checked_time > @frequency
  38.       end
  39.       Mongrel::Uploads.send(action, upload_id, *args)
  40.       Mongrel::Uploads.update_checked_time(upload_id) unless action == :finish
  41.     end
  42. end
  43.  
  44. # Keeps track of the status of all currently processing uploads
  45. class Mongrel::UploadProgress
  46.   attr_accessor :debug
  47.   def initialize
  48.     @guard = Mutex.new
  49.     @counters = {}
  50.   end
  51.  
  52.   def check(upid)
  53.     @counters[upid].last rescue nil
  54.   end
  55.  
  56.   def last_checked(upid)
  57.     @counters[upid].first rescue nil
  58.   end
  59.  
  60.   def update_checked_time(upid)
  61.     @guard.synchronize { @counters[upid][0] = Time.now }
  62.   end
  63.  
  64.   def add(upid, size)
  65.     @guard.synchronize do
  66.       @counters[upid] = [Time.now, {:size => size, :received => 0}]
  67.       puts "#{upid}: Added" if @debug
  68.     end
  69.   end
  70.  
  71.   def mark(upid, len)
  72.     return unless status = check(upid)
  73.     puts "#{upid}: Marking" if @debug
  74.     @guard.synchronize { status[:received] = status[:size] - len }
  75.   end
  76.  
  77.   def finish(upid)
  78.     @guard.synchronize do
  79.       puts "#{upid}: Finished" if @debug
  80.       @counters.delete(upid)
  81.     end
  82.   end
  83.  
  84.   def list
  85.     @counters.keys.sort
  86.   end
  87. end
  88.  


После этого создать конфиг для монгрела (по сути обычный ruby-файл), который будет подключать этот плагин к монгрелу:
touch config/mongrel_upload_progress.conf


Поместить в mongrel_upload_progress.conf нужно следующее:
Copy Source | Copy HTML
  1. require 'progress_plugin'
  2. uri "/", :handler => plugin('/handlers/upload', :path_info => '/upload'), :in_front => true
  3.  


path_info это роут который будет перехватываться плагином для отслеживания состояния загрузки.
Форму загрузки и все нужные js-ники можно взять тут.

Осталось просто запустить монгрел с конфигурационным файлом:
mongrel_rails start -S config/mongrel_upload_progress.conf


Индикатор в браузере будет выглядить примерно так:

Если монгрелов несколько можно использовать drb, детали описаны в ссылке ниже.

Никаких косметических изменений в рамках этой статьи я не делал. В более красивом виде индикатор можно посмотреть на rghost.ru

Конечно жаль признать, что все это написано скорей всего зря, так как простым монгрелом сейчас мало кто обходится. Обычно перед монгрелом ставят nginx или вообще заменяют его apache-ем с mod_passenger. Статьи о них я планирую написать попозже.

Список использованной литературы


mongrel upload progress
Теги: ruby on railsmongrelиндикатор загрузкиkronos articles
Хабы: Ruby
Всего голосов 24: ↑19 и ↓5 +14
Комментарии 15
Комментарии Комментарии 15

Похожие публикации

Лучшие публикации за сутки