Soru Ruby on Rails 3: Rails üzerinden müşteriye veri akışı


RackSpace bulut dosyalarıyla iletişim kuran bir Ruby on Rails uygulaması üzerinde çalışıyorum (Amazon S3'e benzer, ancak bazı özellikleri eksik).

Nesne başına erişim izinleri ve sorgu dizesi kimlik doğrulaması olmaması nedeniyle, kullanıcılara yapılan yüklemeler bir uygulama aracılığıyla gerçekleştirilmelidir.

Rails 2.3'de, aşağıdaki gibi dinamik olarak bir yanıt oluşturabiliyorsunuz gibi görünüyor:

# 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
}

(dan http://api.rubyonrails.org/classes/ActionController/Base.html#M000464)

Yerine 10_000_000.times... Cloudfiles akış üretme kodumu oraya bırakabilirim.

Sorun şu ki, bu tekniği Rails 3'te kullanmaya çalıştığımda elde ettiğim çıktı.

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

Proc nesnesi gibi görünüyor call yöntem çağrılmıyor mu? Başka fikirlerin var mı?


44
2017-08-17 22:48


Menşei




Cevaplar:


Bu Rails 3'te mevcut değil gibi görünüyor

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

Bu benim denetleyicimde benim için işe yaradı:

self.response_body =  proc{ |response, output|
  output.write "Hello world"
}

16
2017-10-04 16:01



Raylar olarak çalışmaz 3.1. John'un cevabını görün. - m33lky
3.2 de çalışmıyor. Görmek stackoverflow.com/a/4320399/850996 altında - Shyam Habarakada


Atamak response_body yanıt veren bir nesne #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 veya backports gem, bunu daha kompakt kullanarak yazabilirsiniz Enumerator.new:

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

Veri temizlendiğinde ve kullanıldığında Raf işleyicisine ve kullanılan temel sunucuya bağlı olduğuna dikkat edin. Örneğin, Mongrel'in verileri aktaracağını doğruladım, ancak diğer kullanıcılar WEBrick'in örneğin yanıt kapatılana kadar arabelleğe aldığını bildirdiler. Yanıtı yıkamak için zorlamanın bir yolu yoktur.

Rails 3.0.x'te birkaç ek toplama var:

  • Geliştirme modunda, sınıfın yeniden yüklenmesinden kaynaklanan kötü etkileşimler nedeniyle numaralandırmaların sınıflandırmalardan sayılması gibi şeyler yapmak sorun yaratabilir. Bu bir hatayı aç Rails 3.0.x içinde.
  • Raf ve Raylar arasındaki etkileşimde bir hata #each Her istek için iki kez çağrılacak. Bu başka hatayı aç. Aşağıdaki maymun yama ile çalışabilirsiniz:

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

Her iki sorun da HTTP akışının bir seçim çerçevesi olduğu Rails 3.1'de giderilmiştir.

Diğer ortak öneri, self.response_body = proc {|response, output| ...}, Rails 3.0.x'te çalışır, ancak 3.1'de kullanımdan kaldırılmış (ve artık veriyi gerçekte aktaramayacak). Yanıt veren bir nesne atama #each tüm Rails 3 sürümlerinde çalışır.


69
2017-12-01 01:17



paha biçilmez cevap, teşekkür ederim. Csv dosyası için akış şablonları uygulamak için kullanılır: github.com/fawce/csv_builder - fawce
Çok teşekkürler. Bu yöntemler neden kullanılmıyor ve veri akışı için resmi bir yol yok ?! - m33lky
maalesef bu çözüm benim için çalışmıyor. Burada yeni bir tartışma başlattım bağlantı - dc10
John, yukarıdaki kodla hafıza sorunumuz var. Büyük miktarda veri akışı yaparsak, büyük bir RAM tüketecek ve asla bırakmayacak gibi görünüyor. Yolcu 3.0.19 altında çalışıyoruz. Bu sorun var mı? - DrChanimal
Hala çalışmıyorsa (Rails 3.1.x), "Last-Modified" başlığını eklemeyi deneyin (Exequiel'in yanıtına bakın) - joel1di1


Yukarıdaki tüm yayınlara teşekkürler, burada büyük CSV'leri akışı için tamamen çalışıyoruz. Bu kod:

  1. İlave taşlar gerektirmez.
  2. Tüm eşleşen nesnelerle bellek bloğunu engellememek için Model.find_each () öğesini kullanır.
  3. Raylar üzerinde test edilmiştir 3.2.5, ruby 1.9.3 ve tek dyno ile tek boynuzlu at kullanarak heroku.
  4. Her 500 satırda bir GC.start ekler, böylelikle heroku dyno'yu uçurmayacak şekilde. izin verilen bellek.
  5. Modelinizin bellek ayak izine bağlı olarak GC.start'ı ayarlamanız gerekebilir. Bunu 105K modellerini herhangi bir problem yaşamadan 9.7MB'lik bir csv'ye aktarmak için başarıyla kullandım.

Denetleyici yöntemi: 

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

23
2017-07-08 21:25





Response_body öğesine #each yöntemine yanıt veren bir nesne atarsanız ve yanıt kapatılana kadar arabelleğe alınıyorsa, eylem denetleyicide şunu deneyin:

self.response.headers ['Son Modifiye'] = Zaman.now.to_s


7
2018-04-20 20:02



Bu benim için çözüm oldu! Bununla birlikte, zamanı şu şekilde biçimlendirmem gerekiyordu: Time.now.ctime.to_s - Matt Fordham
Bu cevabı bulmak için bir süre aradım. Neden üstbilgiyi belirtmediğiniz, niçin akıp gitmediğini anlamıyorum ... yine de, bu satırı eklemek benim için çalıştı. tx - joel1di1


Sadece kayıt için, raylar> = 3.1, denetleyicinin yanıtına #each yöntemine yanıt veren bir nesne atayarak veri akışı için kolay bir yol sunar.

Burada her şey açıklanmaktadır: http://blog.sparqcode.com/2012/02/04/streaming-data-with-rails-3-1-or-3-2/


5
2018-03-14 10:00





Evet, response_body şu an için bunu yapmanın 3 yolu: https://rails.lighthouseapp.com/projects/8994/tickets/4554-render-text-proc-regression


2
2017-10-08 01:12





Bu benim de sorunumu çözdü - Ben gzip'd CSV dosyaları var, kullanıcıya fermuarını açılmış CSV olarak göndermek istiyorum, bu yüzden bir GzipReader kullanarak bir kerede bir satır oku.

Bu satırlar ayrıca, indirme olarak büyük bir dosya dağıtmaya çalışıyorsanız da yararlıdır:

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


2
2017-08-30 23:28





Buna ek olarak, 'İçerik Uzunluğu' kendiniz tarafından başlık.

Aksi takdirde, Raf uzunluğunu belirlemek için beklemek zorunda kalacaktır (gövde verilerini arabelleğe alacak şekilde). Ve yukarıda açıklanan yöntemleri kullanarak çabalarınızı mahvedecek.

Benim durumumda, uzunluğu belirleyebilirim. Yapamayacağınız durumlarda, vücudunuzu göndermeden başlamak için Rack yapmalısınız. 'İçerik Uzunluğu' başlığı. 'Run' önce 'gerekli' sonra config.ru "Rack :: Chunked kullanın" içine eklemek için deneyin. (Teşekkürler arkadiy)


2
2018-06-27 22:59



Uzunluğunu bilmiyorsanız, config.ru dosyasına "run" den önce "require" sonra "Rack :: Chunked" yazın. - arkadiy


Fenerbahçe biletine yorum yaptım, sadece self.response_body = proc yaklaşımının benim için çalıştığını söylemek istedim, ancak başarılı olmak için WEBrick yerine Mongrel kullanmam gerekiyordu.

kırlangıç


1
2017-11-09 14:19