#stream
En ocasiones queremos empezar a enviar datos mientras se esta generando aún el cuerpo de la respuesta. Puede que incluso queramos mantener el envío hasta que el cliente cierra la conexión.
Para eso podemos usar el stream
helper:
[~/sinatra/sinatra-streaming/intro-streaming(master)]$ cat legendary.rb require 'sinatra' before do content_type 'text/plain' end get '/' do stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" sleep 1 out << "- dary!\n" end end
El objeto out
que el método stream
pasa al bloque
es un objeto de la clase
Sinatra::Helpers::Stream.
Este objeto representa el cuerpo de la respuesta en el caso en que se use stream
.
El método stream
permite enviar datos al cliente
incluso cuando el cuerpo del cliente no ha sido generado completamente.
Esto nos permite implementar
Nótese que la conducta de streaming, en particular el número de solicitudes concurrentes, depende en gran medida del servidor web que sirve la aplicación.
sinatra-contrib
improves the
streaming API by making the stream object immitate an IO
object,
making the body play nicer
with middleware unaware of streaming.
This is useful when passing the stream object to a library expecting an IO or StringIO object.
06:31][~/srcSTW/streaming/upandrunning_streaming]$ cat -n simple.rb 1 # http://www.sinatrarb.com/contrib/streaming.html 2 # $ gem install sinatra-contrib 3 require 'sinatra' 4 require 'sinatra/streaming' 5 set server: 'thin' 6 #set server: 'unicorn' 7 8 get '/' do 9 stream do |out| 10 puts out.methods 11 out.puts "Hello World!", "How are you?" 12 out.write "Written #{out.pos} bytes so far!\n" 13 out.putc(65) unless out.closed? 14 out.flush 15 end 16 end
out
es un objeto
Sinatra::Helpers::Stream.
Use the top-level helpers
method to introduce
the methods in the module Sinatra::Streaming
for use in route handlers and templates:
require "sinatra/base" require "sinatra/streaming" class MyApp < Sinatra::Base helpers Sinatra::Streaming end
stream
el parámetro opcional keep_open
se pone a true
close
no será llamado al finalizar el bloque,
permitiendo su cierre en un punto posterior del flujo de
ejecución.
Broadly speaking, there are two ways to handle concurrent requests to a server:
El siguiente ejemplo muestra como mantener una conexión persitente, abierta para enviar mensajes de broadcast a unos suscriptores:
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ cat a_simple_streaming_example.rb require 'sinatra' before do content_type :txt end set server: 'thin', connections: [] get '/consume' do stream(:keep_open) do |out| # store connection for later on settings.connections << out logger.warn "connections.length = #{settings.connections.length}" # remove connection when closed properly out.callback do logger.warn "connection closed. out = #{out}" settings.connections.delete(out) logger.warn "connections.length = #{settings.connections.length}" end # remove connection when due to an error out.errback do logger.warn "we just lost connection!. out = #{out}" settings.connections.delete(out) logger.warn "connections.length = #{settings.connections.length}" end end # stream end get '/produce/:message' do settings.connections.each do |out| out << "#{Time.now} -> #{params[:message]}" << "\n" end "Sent #{params[:message]} to all clients." endPara usar este ejemplo utilizaremos este
Rakefile
:
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ cat Rakefile task :default => :server desc "run the server for the stream(:keep_open) example" task :server do sh "ruby a_simple_streaming_example.rb" end desc "visit with browser 'localhost:4567/consume'" task :consume do sh "open http://localhost:4567/consume" end desc "send messages to the consumer" task :produce do (1..10).each do |i| sh "sleep 1; curl http://localhost:4567/produce/#{i}%0D" end end desc "start both consumer and producer" task :all => [ :consume, :produce ]
thin
.
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ rake server ruby a_simple_streaming_example.rb == Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin Thin web server (v1.6.1 codename Death Proof) Maximum connections set to 1024 Listening on localhost:4567, CTRL+C to stop
localhost:4567/consume
.
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ rake consume open http://localhost:4567/consumeEsto abre (en MacOS X) un navegador que queda a la espera del servidor de que la ruta de producción genere algún contenido
localhost:4567/consume
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ rake produce sleep 1; curl http://localhost:4567/produce/1%0D to all clients.sleep 1; curl http://localhost:4567/produce/2%0D to all clients.sleep 1; curl http://localhost:4567/produce/3%0D to all clients.sleep 1; curl http://localhost:4567/produce/4%0D to all clients.sleep 1; curl http://localhost:4567/produce/5%0D to all clients.sleep 1; curl http://localhost:4567/produce/6%0D to all clients.sleep 1; curl http://localhost:4567/produce/7%0D to all clients.sleep 1; curl http://localhost:4567/produce/8%0D to all clients.sleep 1; curl http://localhost:4567/produce/9%0D to all clients.sleep 1; curl http://localhost:4567/produce/10%0D to all clients.
localhost:4567/consume
aparezca algo parecido a esto:
2013-11-22 22:42:24 +0000 -> 1 2013-11-22 22:42:25 +0000 -> 2 2013-11-22 22:42:26 +0000 -> 3 2013-11-22 22:42:27 +0000 -> 4 2013-11-22 22:42:28 +0000 -> 5 2013-11-22 22:42:29 +0000 -> 6 2013-11-22 22:42:30 +0000 -> 7 2013-11-22 22:42:31 +0000 -> 8 2013-11-22 22:42:32 +0000 -> 9 2013-11-22 22:42:33 +0000 -> 10
En la consola del servidor aparecerá algo parecido a esto:
[~/sinatra/sinatra-streaming/upandrunning-streaming(master)]$ rake ruby a_simple_streaming_example.rb == Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin Thin web server (v1.6.1 codename Death Proof) Maximum connections set to 1024 Listening on localhost:4567, CTRL+C to stop W, [2013-11-22T22:46:17.132773 #21927] WARN -- : connections.length = 1 W, [2013-11-22T22:47:16.655453 #21927] WARN -- : connection closed. out = #<Sinatra::Helpers::Stream:0x007ff10a9ed7b8> W, [2013-11-22T22:47:16.655557 #21927] WARN -- : connections.length = 0 W, [2013-11-22T22:47:16.655620 #21927] WARN -- : we just lost connection!. out = #<Sinatra::Helpers::Stream:0x007ff10a9ed7b8> W, [2013-11-22T22:47:16.655677 #21927] WARN -- : connections.length = 0 127.0.0.1 - - [22/Nov/2013 22:47:16] "GET /consume HTTP/1.1" 200 - 59.5292 127.0.0.1 - - [22/Nov/2013 22:50:32] "GET /produce/1%0D HTTP/1.1" 200 23 0.0009 127.0.0.1 - - [22/Nov/2013 22:50:33] "GET /produce/2%0D HTTP/1.1" 200 23 0.0008 127.0.0.1 - - [22/Nov/2013 22:50:34] "GET /produce/3%0D HTTP/1.1" 200 23 0.0009 127.0.0.1 - - [22/Nov/2013 22:50:35] "GET /produce/4%0D HTTP/1.1" 200 23 0.0008 127.0.0.1 - - [22/Nov/2013 22:50:36] "GET /produce/5%0D HTTP/1.1" 200 23 0.0009 127.0.0.1 - - [22/Nov/2013 22:50:37] "GET /produce/6%0D HTTP/1.1" 200 23 0.0009 127.0.0.1 - - [22/Nov/2013 22:50:38] "GET /produce/7%0D HTTP/1.1" 200 23 0.0009 127.0.0.1 - - [22/Nov/2013 22:50:39] "GET /produce/8%0D HTTP/1.1" 200 23 0.0007 127.0.0.1 - - [22/Nov/2013 22:50:40] "GET /produce/9%0D HTTP/1.1" 200 23 0.0006 127.0.0.1 - - [22/Nov/2013 22:50:41] "GET /produce/10%0D HTTP/1.1" 200 24 0.0009
El objeto out
de la clase
Sinatra::Helpers::Stream.
dispone de los métodos
#callback(&block) => Object
y errback
(parece que errback
es un alias de callback
).
# File 'lib/sinatra/base.rb' def callback(&block) return yield if @closed @callbacks << block end
(Object) callback(&block)
.
y (Object) errback(&block)
(Object) callback(&block)
specifies a block to be executed if and when the Deferrable object receives a status of :succeeded
.
out.callback do logger.warn "connection closed. out = #{out}" settings.connections.delete(out) logger.warn "connections.length = #{settings.connections.length}" end
(Object) errback(&block)
specifies a block to be executed if and when the Deferrable object receives
a status of :failed
.
out.errback do logger.warn "we just lost connection!. out = #{out}" settings.connections.delete(out) logger.warn "connections.length = #{settings.connections.length}" end