Pull to refresh

Индикатор загрузки файлов в веб-приложениях на 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


Этап подготовки завершен. Теперь надо немного пояснить каким образом фунционирует индикатор загрузки файлов. Сабмит формы должен идти в 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. def index
  4. @upid = Time.now.tv_sec.to_s
  5. end
  6. def upload
  7. render :text => 'done'
  8. end
  9. def progress
  10. @status = Mongrel::Uploads.check(params[:upload_id])
  11. respond_to do |format|
  12. format.js
  13. end
  14. end
  15. 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. def initialize(options = {})
  4. @path_info = Array(options[:path_info])
  5. @frequency = options[:frequency] || 3
  6. @request_notify = true
  7. if options[:drb]
  8. require 'drb'
  9. DRb.start_service
  10. Mongrel.const_set :Uploads, DRbObject.new(nil, options[:drb])
  11. else
  12. Mongrel.const_set :Uploads, Mongrel::UploadProgress.new
  13. end
  14. Mongrel::Uploads.debug = true if options[:debug]
  15. end
  16. def request_begins(params)
  17. upload_notify(:add, params, params[Mongrel::Const::CONTENT_LENGTH].to_i)
  18. end
  19. def request_progress(params, clen, total)
  20. upload_notify(:mark, params, clen)
  21. end
  22. def process(request, response)
  23. upload_notify(:finish, request.params)
  24. end
  25. private
  26. def upload_notify(action, params, *args)
  27. return unless @path_info.include?(params['PATH_INFO']) &&
  28. params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
  29. upload_id = Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
  30. if action == :mark
  31. last_checked_time = Mongrel::Uploads.last_checked(upload_id)
  32. return unless last_checked_time && Time.now - last_checked_time > @frequency
  33. end
  34. Mongrel::Uploads.send(action, upload_id, *args)
  35. Mongrel::Uploads.update_checked_time(upload_id) unless action == :finish
  36. end
  37. end
  38. # Keeps track of the status of all currently processing uploads
  39. class Mongrel::UploadProgress
  40. attr_accessor :debug
  41. def initialize
  42. @guard = Mutex.new
  43. @counters = {}
  44. end
  45. def check(upid)
  46. @counters[upid].last rescue nil
  47. end
  48. def last_checked(upid)
  49. @counters[upid].first rescue nil
  50. end
  51. def update_checked_time(upid)
  52. @guard.synchronize { @counters[upid][0] = Time.now }
  53. end
  54. def add(upid, size)
  55. @guard.synchronize do
  56. @counters[upid] = [Time.now, {:size => size, :received => 0}]
  57. puts "#{upid}: Added" if @debug
  58. end
  59. end
  60. def mark(upid, len)
  61. return unless status = check(upid)
  62. puts "#{upid}: Marking" if @debug
  63. @guard.synchronize { status[:received] = status[:size] - len }
  64. end
  65. def finish(upid)
  66. @guard.synchronize do
  67. puts "#{upid}: Finished" if @debug
  68. @counters.delete(upid)
  69. end
  70. end
  71. def list
  72. @counters.keys.sort
  73. end
  74. end


После этого создать конфиг для монгрела (по сути обычный 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


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

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


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

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

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

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

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


mongrel upload progress
Tags:ruby on railsmongrelиндикатор загрузкиkronos articles
Hubs: Ruby
Total votes 24: ↑19 and ↓5 +14
Views2.4K

Comments 15

Only those users with full accounts are able to leave comments. Log in, please.

Popular right now