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を利用

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

References

results matching ""

    No results matching ""