Ответ 1
Похоже, что это невозможно в Rails 3
https://rails.lighthouseapp.com/projects/8994/tickets/2546-render-text-proc
Это оказалось для меня в моем контроллере:
self.response_body = proc{ |response, output|
output.write "Hello world"
}
Я работаю над приложением Ruby on Rails, которое общается с облачными облаками RackSpace (аналогично Amazon S3, но не имеет некоторых функций).
Из-за отсутствия доступности разрешений доступа к объектам и проверки подлинности строки запроса загрузка пользователям должна выполняться через приложение.
В Rails 2.3, похоже, вы можете динамически строить ответ следующим образом:
# Streams about 180 MB of generated data to the browser.
render :text => proc { |response, output|
10_000_000.times do |i|
output.write("This is line #{i}\n")
end
}
(из http://api.rubyonrails.org/classes/ActionController/Base.html#M000464)
Вместо 10_000_000.times...
я мог бы сбросить код генерации потока облачных файлов.
Проблема в том, что это результат, который я получаю, когда пытаюсь использовать эту технику в Rails 3.
#<Proc:[email protected]/Users/jderiksen/lt/lt-uber/site/app/controllers/prospect_uploads_controller.rb:75>
Похоже, может быть, метод proc object call
не вызывается? Любые другие идеи?
Похоже, что это невозможно в Rails 3
https://rails.lighthouseapp.com/projects/8994/tickets/2546-render-text-proc
Это оказалось для меня в моем контроллере:
self.response_body = proc{ |response, output|
output.write "Hello world"
}
Назначьте response_body
объект, который отвечает на #each
:
class Streamer
def each
10_000_000.times do |i|
yield "This is line #{i}\n"
end
end
end
self.response_body = Streamer.new
Если вы используете 1.9.x или Backports, вы можете записать это более компактно, используя Enumerator.new
:
self.response_body = Enumerator.new do |y|
10_000_000.times do |i|
y << "This is line #{i}\n"
end
end
Обратите внимание, что когда и если данные сброшены, зависит от используемого обработчика Rack и используемого сервера. Я подтвердил, что Mongrel, например, будет передавать данные, но другие пользователи сообщили, что WEBrick, например, буферизирует его до тех пор, пока ответ не будет закрыт. Невозможно принудительно отключить ответ.
В Rails 3.0.x есть несколько дополнительных исправлений:
Ошибка при взаимодействии между Rack и Rails вызывает вызов #each
дважды для каждого запроса. Это еще одна ошибка . Вы можете обойти его со следующим патчем обезьяны:
class Rack::Response
def close
@body.close if @body.respond_to?(:close)
end
end
Обе проблемы исправлены в Rails 3.1, где потоковая передача HTTP - это свойство выделения.
Обратите внимание, что другое общее предложение self.response_body = proc {|response, output| ...}
работает в Rails 3.0.x, но оно устарело (и больше не будет фактически передавать данные) в 3.1. Назначение объекта, отвечающего на #each
, работает во всех версиях Rails 3.
Благодаря всем вышеперечисленным сообщениям, здесь приведен полный рабочий код для потоковой передачи больших CSV. Этот код:
Метод контроллера:
def csv_export
respond_to do |format|
format.csv {
@filename = "responses-#{Date.today.to_s(:db)}.csv"
self.response.headers["Content-Type"] ||= 'text/csv'
self.response.headers["Content-Disposition"] = "attachment; filename=#{@filename}"
self.response.headers['Last-Modified'] = Time.now.ctime.to_s
self.response_body = Enumerator.new do |y|
i = 0
Model.find_each do |m|
if i == 0
y << Model.csv_header.to_csv
end
y << sr.csv_array.to_csv
i = i+1
GC.start if i%500==0
end
end
}
end
end
конфигурации /unicorn.rb
# Set to 3 instead of 4 as per http://michaelvanrooijen.com/articles/2011/06/01-more-concurrency-on-a-single-heroku-dyno-with-the-new-celadon-cedar-stack/
worker_processes 3
# Change timeout to 120s to allow downloading of large streamed CSVs on slow networks
timeout 120
#Enable streaming
port = ENV["PORT"].to_i
listen port, :tcp_nopush => false
Model.rb
def self.csv_header
["ID", "Route", "username"]
end
def csv_array
[id, route, username]
end
Если вы назначаете response_body объект, который отвечает на метод #each, и он буферизует до тех пор, пока ответ не будет закрыт, попробуйте в контроллере действий:
self.response.headers ['Last-Modified'] = Time.now.to_s
Только для записи rails >= 3.1 имеет простой способ потоковой передачи данных путем назначения объекта, отвечающего на метод #each, на ответ контроллера.
Все объяснено здесь: http://blog.sparqcode.com/2012/02/04/streaming-data-with-rails-3-1-or-3-2/
Да, response_body - это способ Rails 3 сделать это на данный момент: https://rails.lighthouseapp.com/projects/8994/tickets/4554-render-text-proc-regression
Это также решило мою проблему - у меня есть файлы gzip'd CSV, которые вы хотите отправить пользователю как распакованный CSV, поэтому я читал их по одной строке за раз, используя GzipReader.
Эти строки также полезны, если вы пытаетесь загрузить большой файл в качестве загрузки:
self.response.headers["Content-Type"] = "application/octet-stream"
self.response.headers["Content-Disposition"] = "attachment; filename=#{filename}"
Кроме того, вам нужно будет установить заголовок "Content-Length" самостоятельно.
Если нет, Rack придется ждать (буферизировать данные тела в память), чтобы определить длину. И это испортит ваши усилия, используя описанные выше методы.
В моем случае я мог бы определить длину. В тех случаях, когда вы не можете, вы должны сделать Rack, чтобы начать отправку тела без заголовка Content-Length. Попробуйте добавить в config.ru "использовать Rack:: Chunked" после "require" перед "run". (Спасибо arkadiy)
Я прокомментировал билет на маяк, просто хотел сказать, что подход self.response_body = proc работал у меня, хотя мне нужно было использовать Mongrel вместо WEBrick для успеха.
Martin
Применение метода Джона вместе с предложением Exequiel сработало для меня.
Утверждение
self.response.headers['Last-Modified'] = Time.now.to_s
отмечает ответ как не кэшируемый в стойке.
После изучения далее я решил, что можно также использовать это:
headers['Cache-Control'] = 'no-cache'
Это, для меня, немного интуитивно. Он передает сообщение любому, кто может читать мой код. Кроме того, в случае, если будущая версия стойки перестает проверять Last-Modified, много кода может сломаться, и может быть время для людей выяснить, почему.