Rails streaming
- rails4以前 - ActionController::Streaming
- rails4以後 - ActionController::Live
ストリーミングダウンロード
データがサーバ上の実ファイルではない場合、Railsで順次送信するしかありません。 データの生成に時間がかかる場合(巨大なPDFを作る、別サーバからダウンロードしたデータを順次流すなど)、すべてのデータが準備できる前に送信開始したいものです。
その場合は、response.streamを使用します。 http://api.rubyonrails.org/classes/ActionController/Streaming.html
注意点
- なるべくContent-Lengthを指定する- 事前にContent-Lengthを指定しておけば、ブラウザ側で「何%ダウンロード中…」とプログレス
 
- streaming 対応サーバーを利用する- webrickはだめ ->> localのテストでのだめな気がする
 
sample1 - streamingを利用
- https://gist.github.com/lexmag/3259481
- Streaming Large Data Responses with Rails
- Stream CSV Files In Rails Because You Can
- ActiveAdminでCSVダウンロード機能をカスタマイズする
class PostsController < ApplicationController
  # rails3
  def stream
    headers['X-Accel-Buffering'] = 'no' # Stop NGINX from buffering
    headers['Cache-Control'] = 'no-cache'
    headers['Content-Type'] = 'text/csv'
    # headers['Content-Type'] = 'text/event-stream'
    headers["Transfer-Encoding"] = "chunked" # Chunked response header
    headers["Content-Disposition"] = %(attachment; filename="#{csv_filename}")
    headers["Last-Modified"] = Time.zone.now.ctime.to_s
    headers.delete("Content-Length") # See one line above
    self.response_body = Enumerator.new do |y|
      loop do
        if (Time.current.sec % 5).zero?
          y << "event: counter\n"
          y << "data: 5 seconds passed\n\n"
        end
        sleep 1
      end
    end
  end
  # http://stackoverflow.com/questions/3507594/ruby-on-rails-3-streaming-data-through-rails-to-client/11386840
  def stream_csv
  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
  # rails 4以降
  # The new approach
  # include ActionController::Live
  def stream
    headers['Content-Type'] = 'text/event-stream'
    begin
      loop do
        if (Time.current.sec % 5).zero?
          response.stream.write("event: counter\n")
          response.stream.write("data: 5 seconds passed\n\n")
        end
        sleep 1
      end
    rescue IOError
      # Catch when the client disconnects
    ensure
      response.stream.close
    end
  end
  # ...
end
原理は?
- calling next on the enumerator to get the next chunk of data
sample 2 - s3 fileをdownload
http://stackoverflow.com/questions/12277971/using-send-file-to-download-a-file-from-amazon-s3
def download
  data = open("https://s3.amazonaws.com/PATTH TO YOUR FILE") 
  send_data data.read, filename: "NAME YOU WANT.pdf", type: "application/pdf", disposition: 'inline', stream: 'true', buffer_size: '4096' 
end
- s3 -> server, server -> clientへの2回のdownloadが発生するのは非効率でき
sample 3 - usgin expiring S3 URL
http://stackoverflow.com/questions/12277971/using-send-file-to-download-a-file-from-amazon-s3