46

RackSpace クラウドファイルと通信する Ruby on Rails アプリに取り組んでいます (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...Cloudfiles ストリーム生成コードをそこにダンプする代わりに。

問題は、Rails 3 でこの手法を使用しようとしたときに得られる出力です。

#<Proc:0x000000010989a6e8@/Users/jderiksen/lt/lt-uber/site/app/controllers/prospect_uploads_controller.rb:75>

proc オブジェクトのcallメソッドが呼び出されていないように見えますか? 他のアイデアはありますか?

4

10 に答える 10

70

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 gemを使用している場合は、以下を使用してよりコンパクトに記述できますEnumerator.new

self.response_body = Enumerator.new do |y|
  10_000_000.times do |i|
    y << "This is line #{i}\n"
  end
end

データがいつフラッシュされるかは、使用されているラック ハンドラーと基礎となるサーバーによって異なることに注意してください。たとえば、Mongrel がデータをストリーミングすることを確認しましたが、他のユーザーは、たとえば WEBrick は応答が閉じられるまでデータをバッファリングすると報告しています。応答を強制的にフラッシュする方法はありません。

Rails 3.0.x では、追加の問題がいくつかあります。

  • 開発モードでは、列挙内からモデル クラスにアクセスするなどの操作を行うと、クラスの再読み込みとの不適切な相互作用が原因で問題が発生する可能性があります。これはRails 3.0.xの未解決のバグです。
  • Rack と Rails 間の相互作用のバグにより#each、各リクエストに対して が 2 回呼び出されます。これは別の未解決のバグです。次のモンキー パッチで回避できます。

    class Rack::Response
      def close
        @body.close if @body.respond_to?(:close)
      end
    end
    

どちらの問題も、Rails 3.1 で修正されています。Rails 3.1 では、HTTP ストリーミングが重要な機能です。

もう 1 つの一般的な提案でself.response_body = proc {|response, output| ...}ある は、Rails 3.0.x では機能しますが、3.1 では非推奨になっています (実際にはデータをストリーミングしません)。応答するオブジェクトの割り当ては、#eachRails 3 のすべてのバージョンで機能します。

于 2010-12-01T01:17:04.177 に答える
24

上記のすべての投稿のおかげで、大きな CSV をストリーミングするための完全に機能するコードがここにあります。このコード:

  1. 追加の宝石は必要ありません。
  2. Model.find_each() を使用して、一致するすべてのオブジェクトでメモリを肥大化させないようにします。
  3. Rails 3.2.5、ruby 1.9.3、heroku で Unicorn を使用し、単一の dyno でテストされています。
  4. heroku dyno の許可されたメモリを消費しないように、500 行ごとに GC.start を追加します。
  5. モデルのメモリ フットプリントに応じて、GC.start を調整する必要がある場合があります。これを使用して、105K モデルを 9.7MB の 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

config/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
于 2012-07-08T21:25:29.603 に答える
16

これはRails 3では利用できないようです

https://rails.lighthouseapp.com/projects/8994/tickets/2546-render-text-proc

これは私のコントローラーでうまくいくように見えました:

self.response_body =  proc{ |response, output|
  output.write "Hello world"
}
于 2010-10-04T16:01:31.307 に答える
8

#each メソッドに応答するオブジェクトを response_body に割り当てていて、応答が閉じられるまでバッファリングしている場合は、アクション コントローラーで試してください。

self.response.headers['Last-Modified'] = Time.now.to_s

于 2012-04-20T20:02:31.910 に答える
5

記録として、Rails >= 3.1 には、#each メソッドに応答するオブジェクトをコントローラーの応答に割り当てることで、データをストリーミングする簡単な方法があります。

すべてがここで説明されています: http://blog.sparqcode.com/2012/02/04/streaming-data-with-rails-3-1-or-3-2/

于 2012-03-14T10:00:15.897 に答える
2

これで私の問題も解決しました-gzipされたCSVファイルがあり、解凍されたCSVとしてユーザーに送信したいので、GzipReaderを使用して一度に1行ずつ読み取ります。

これらの行は、ダウンロードとして大きなファイルを配信しようとしている場合にも役立ちます。

self.response.headers["Content-Type"] = "application/octet-stream" self.response.headers["Content-Disposition"] = "attachment; filename=#{filename}"

于 2011-08-30T23:28:35.270 に答える
2

さらに、「Content-Length」ヘッダーを自分で設定する必要があります。

そうでない場合、ラックは長さを決定するために待機する必要があります (本体データをメモリにバッファリングします)。そして、それは上記の方法を使用してあなたの努力を台無しにします.

私の場合、長さを決定できました。できない場合は、「Content-Length」ヘッダーなしでボディの送信を開始するように Rack を作成する必要があります。config.ru に「require」の後「run」の前に「use Rack::Chunked」を追加してみてください。(ありがとうアルカディ)

于 2012-06-27T22:59:18.603 に答える
2

はい、response_body は今のところこれを行う Rails 3 の方法です: https://rails.lighthouseapp.com/projects/8994/tickets/4554-render-text-proc-regression

于 2010-10-08T01:12:33.077 に答える
1

灯台のチケットにコメントしました。成功するにはWEBrickの代わりにMongrelを使用する必要がありましたが、self.response_body=procアプローチが機能したと言いたかっただけです。

マーティン

于 2010-11-09T14:19:16.040 に答える
1

ジョンの解決策をエクセキエルの提案と一緒に適用することは私のために働いた。

ステートメント

self.response.headers['Last-Modified'] = Time.now.to_s

応答をラックにキャッシュ不可としてマークします。

さらに調査した後、私はこれも使用できると考えました:

headers['Cache-Control'] = 'no-cache'

これは、私にとっては、もう少し直感的です。私のコードを読んでいる可能性のある他の人にメッセージを伝えます。また、ラックの将来のバージョンでLast-Modifiedのチェックが停止した場合、多くのコードが破損する可能性があり、人々がその理由を理解するのにしばらく時間がかかる可能性があります。

于 2013-01-16T06:11:41.643 に答える