stream multiple body using async sinatra - ruby

I would like start a long poll request from javascript which is fine and i expect my ruby prog to stream multiple body sections to the javascript. Why doesn the following (pseudo)code work?
require 'rubygems'
require 'sinatra/async'
require 'eventmachine'
require 'thin'
require 'json'
class Test < Sinatra:Base
register Sinatra::Async
aget '/process' do
for c in 1..10
body {
{ :data => [ "this is part #{c}" ] }.to_json
end
end
end
run!
end
Maybe i misunderstood what long polling and async is supposed to do, but my expectation is that i get multiple bodies sent back to the client ? Do i need to use eventmachine or something?
thanks

require 'rubygems'
require 'sinatra/async'
require 'thin'
require 'json'
class Test < Sinatra::Base
register Sinatra::Async
class JSONStream
include EventMachine::Deferrable
def stream(object)
#block.call object.to_json + "\n"
end
def each(&block)
#block = block
end
end
aget '/process' do
puts 'ok'
out = JSONStream.new
body out
EM.next_tick do
c = 0
timer = EM.add_periodic_timer(0.3) do
c += 1
out.stream :data => ["this is part #{c}"]
if c == 100
timer.cancel
out.succeed
end
end
end
end
run!
end
See also: http://confreaks.net/videos/564-scotlandruby2011-real-time-rack

It appears in the example below that you need an EventMachine event to trigger the sending of the multiple bodies. Also see this previous answer as well.
require 'sinatra/async'
class AsyncTest < Sinatra::Base
register Sinatra::Async
aget '/' do
body "hello async"
end
aget '/delay/:n' do |n|
EM.add_timer(n.to_i) { body { "delayed for #{n} seconds" } }
end
end

Related

How to handle object from 'PUT' request in Rack

In a basic Rack server how do I get data from a put request?
For example my test is like this:
require 'minitest/autorun'
require 'rack/test'
require_relative './myserver-rack'
class MyApp < Minitest::Test
include Rack::Test::Methods
def app
App.new
end
def test_put
put '/data/foo', 'my object'
end
end
Then in my server Rack file I have this but I need to be able to get the my object data from the put request to pass to my method later:
...
def call(env)
mydata = "??" # need to put something here to get my data
p = env['PATH_INFO']
case env['REQUEST_METHOD']
when 'PUT'
put(p, mydata)
end
end
...
end

How to test if some specific rack middleware is being used?

To be more particular, I'm talking about sentry-raven and sinatra here. I saw examples testing sinatra applications, or middlewares. But I didn't see ones testing if some particular middleware is present. Or should I be testing behavior, not configuration (or how should I call it)?
The important thing (I'd say) is the behaviour, but if you wish to check for middleware there are 2 ways I'd suggest after taking a delve into the Sinatra source (there are possibly much easier/better ways):
The env
In the Sinatra source there's a method that uses the env to check if a middleware is already present:
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? #app.call(env) : super
end
You could do the same thing in a route, e.g.
get "/env-keys" do
env.keys.inspect
end
It'll only show you the middleware if it's inserted something in env hash, e.g.
class MyBad
def initialize app, options={}
#app = app
#options = options
end
def call env
#app.call env.merge("mybad" => "I'm sorry!")
end
end
output:
["SERVER_SOFTWARE", "SERVER_NAME", "rack.input", "rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "REQUEST_METHOD", "REQUEST_PATH", "PATH_INFO", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT", "HTTP_USER_AGENT", "HTTP_DNT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "GATEWAY_INTERFACE", "SERVER_PORT", "QUERY_STRING", "SERVER_PROTOCOL", "rack.url_scheme", "SCRIPT_NAME", "REMOTE_ADDR", "async.callback", "async.close", "rack.logger", "mybad", "rack.request.query_string", "rack.request.query_hash", "sinatra.route"]
It's near the end of that list.
The middleware method
There's also a method called middleware in Sinatra::Base:
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + #middleware
else
#middleware
end
end
Call it in the class definition of a modular app and you can get the middlewares in an array:
require 'sinatra/base'
class AnExample < Sinatra::Base
use MyBad
warn "self.middleware = #{self.middleware}"
output:
self.middleware = [[MyBad, [], nil]]
There may be a way to get it from Sinatra::Application, but I haven't looked.
With help from ruby-raven guys, we've got this:
ENV['RACK_ENV'] = 'test'
# the app: start
require 'sinatra'
require 'sentry-raven'
Raven.configure(true) do |config|
config.dsn = '...'
end
use Raven::Rack
get '/' do
'Hello, world!'
end
# the app: end
require 'rspec'
require 'rack/test'
Raven.configure do |config|
logger = ::Logger.new(STDOUT)
logger.level = ::Logger::WARN
config.logger = logger
end
describe 'app' do
include Rack::Test::Methods
def app
#app || Sinatra::Application
end
class TestRavenError < StandardError; end
it 'sends errors to sentry' do
#app = Class.new Sinatra::Application do
get '/' do
raise TestRavenError
end
end
allow(Raven.client).to receive(:send).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven.client).to have_received(:send)
end
end
Or if raven sending requests is in the way (when tests fail because of raven sending requests, and not because of the underlying error), one can disable them:
Raven.configure(true) do |config|
config.should_send = Proc.new { false }
end
And mock Raven.send_or_skip instead:
...
allow(Raven).to receive(:send_or_skip).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven).to have_received(:send_or_skip)
...

Ruby: const_set outside block?

I want to mock a class with Ruby.
How do I write a method that will take care of the boilerplate code?
The following code:
module Mailgun
end
module Acani
def self.mock_mailgun(mock)
temp = Mailgun
const_set(:Mailgun, mock)
p Mailgun
yield
ensure
const_set(:Mailgun, temp)
end
end
Acani.mock_mailgun('mock') { p Mailgun }
prints:
"mock"
Mailgun
What's going on here? Why is Mailgun its original value inside the block? Does this have to do with Ruby bindings?
Ruby version: 2.1.1p76
Try putting Object. before each const_set.
The code in the question is simplified. Here is the pertinent code:
test/test_helper.rb
require 'minitest/autorun'
module Acani
def self.const_mock(const, mock)
temp = const_get(const)
const_set_silent(const, mock)
yield
ensure
const_set_silent(const, temp)
end
private
def self.const_set_silent(const, value)
temp = $VERBOSE
$VERBOSE = nil
Object.const_set(const, value)
ensure
$VERBOSE = temp
end
end
test/web_test.rb
require 'test_helper'
require 'rack/test'
require_relative '../web'
class AppTest < MiniTest::Test
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_password_reset
post '/users', {email: 'user1#gmail.com', password: 'password1'}
mailgun_mock = MiniTest::Mock.new
mailgun_mock.expect(:send, 200, [Hash])
Acani.const_mock(:Mailgun, mailgun_mock) do
post '/password_resets', {email: 'user1#gmail.com'}
end
mailgun_mock.verify
assert_equal 201, last_response.status
end
end

Sinatra + Fibers + EventMachine

I would like to know to to pause a Root Fiber in ruby (if possible).
I have this Sinatra app, and I am making async calls to an external API with EventMachine.
I don't want to respond to the client until the api responds me.
For example, sleeping the Root Fiber in Sinatra until the EventMachine callback wake it up.
Thanks.
get '/some/route/' do
fib = Fiber.current
req = EM::SomeNonBlokingLib.request
req.callback do |response|
fib.resume(response)
end
req.errback do |err|
fib.resume(err)
end
Fiber.yield
end
EDIT
In your case you should spawn a Fiber for each request. So. Firstly create Rack config file and add some magick:
# config.ru
BOOT_PATH = File.expand_path('../http.rb', __FILE__)
require BOOT_PATH
class FiberSpawn
def initialize(app)
#app = app
end
def call(env)
fib = Fiber.new do
res = #app.call(env)
env['async.callback'].call(res)
end
EM.next_tick{ fib.resume }
throw :async
end
end
use FiberSpawn
run Http
Then your http Sinatra application:
# http.rb
require 'sinatra'
require 'fiber'
class Http < Sinatra::Base
get '/' do
f = Fiber.current
EM.add_timer(1) do
f.resume("Hello World")
end
Fiber.yield
end
end
Now you could run it under thin for example:
> thin start -R config.ru
Then if you will visit locakhost:3000 you'll see your Hello World message

async requests using sinatra streaming API

I use async_sinatra gem to implement asynchronous routes, but I came across a post somewhere that said that Sinatra's streaming API can be used instead of async_sinatra for this purpose. Can the same functionality as below be implemented using streaming?
require 'em-hiredis'
require 'sinatra/async'
class App < Sinatra::Base
register Sinatra::Async
def redis
#redis ||= EM::Hiredis.connect
end
aget '/' do
redis.blpop('abcdef', 15).
callback {|x| body "x=#{x}"}.
errback {|e| body "e=#{e}"}
end
run! if app_file == $0
end
to answer my own question:
require 'em-hiredis'
require 'sinatra/base'
class App < Sinatra::Base
def redis
#redis ||= EM::Hiredis.connect
end
get '/' do
stream :keep_open do |out|
redis.blpop('abcdef', 15).callback do |x|
out << "x=#{x}"
out.close
end.errback do |e|
out << "e=#{e}"
out.close
end
end
end
run! if app_file == $0
end

Resources