how to halt from inside stream block in sinatra? - ruby

I am trying to respond with a HTTP error code from my streaming block, but the web server throws an exception. what is the proper way to do it in this context?
/var/lib/gems/1.9.1/gems/sinatra-1.3.3/lib/sinatra/base.rb:803:in `throw':
uncaught throw :halt (ArgumentError)
my code:
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
stream :keep_open do |out|
error 401
end
end
run! if app_file == $0
end

Based on my understanding of #stream(), the response Headers have already been sent. While you can continue streaming data (the body), and even close the connection, I don't think you can modify the header after they've already been sent. I'm digging through Sinatra YARD docs to verify, but I'm pretty sure that is your issue.

Related

How to diagnose why a Sinatra app is throwing NotFound in production but not in development?

I have a Sinatra app in production (running with Puma) that includes a not_found handler:
module MyApp
class Server
not_found do
[404, { 'Content-Type' => 'application/json' }, ['{"error":"not found"}']]
end
end
end
In development, this does what I expect and on missing pages I see a 404 with a JSON error message. However, in production I'm seeing a NotFound exception which triggers an Airbrake alert.
The stack trace is long, but the top line points here:
/gems/sinatra-1.4.5/lib/sinatra/base.rb:1021 in "route_missing"
Which is here:
https://github.com/sinatra/sinatra/blob/v1.4.5/lib/sinatra/base.rb#L1021 ,which does:
def route_missing
if #app
forward
else
raise NotFound
end
end
The comments in the Sinatra source say that the error is forwarded if your app is not middleware, otherwise it is raised. Yet somehow my expectation would be that my not_found handler should be called instead. It is being called in development. How can I best debug why it isn't in production?

Sinatra::Streaming with Rack not chunking response

I'm having a rough time trying to get this simple streaming test to work using Sinatra and Rack.
In my stream.rb file, I have:
require 'sinatra'
require 'sinatra/streaming'
class StreamAPI < Sinatra::Base
helpers Sinatra::Streaming
get '/stream' do
stream do |out|
5.times do
out.puts "Hello!"
sleep 1
end
out.flush
end
end
run! if app_file == $0
end
And in my config.ru I have:
require 'rack'
require './stream.rb'
run StreamAPI
When I curl the url, I get "Hello!" 5 times, but all at once after 5 seconds. Looking at the headers I can see that Transfer-Encoding is set to Chunked. What I want is for the a "Hello!" to come through then another after a 1 second pause.
Edit: Along with the selected answer below, I also needed to add proxy_buffering off; to my NGINX configuration file.
This depends on which server you are using. From the Sinatra README:
Note that the streaming behavior, especially the number of concurrent requests, highly depends on the web server used to serve the application. Some servers, like WEBRick, might not even support streaming at all. If the server does not support streaming, the body will be sent all at once after the block passed to stream finishes executing.
It looks like you are using a server that doesn’t support streaming. If you switch to one that does (e.g. Thin or Puma) this should work.

Savon proxy works in script, not in Rails

I'm using Savon to make calls to a SOAP API. The API I'm accessing requires calls to be coming from a whitelisted IP address, so I'm using a QuotaGuard proxy.
The call that I'm making returns perfectly in IRB and also as a plain ruby script. When I put the exact same code into a method in my Rails model, the call times out because it isn't coming through the proxy IP. QuotaGuard has a dashboard where I can look at requests going through the proxy IP, so I know for sure that this call is not going through.
Here is my ruby script code:
require 'savon'
ping_request = Savon.client do
wsdl "http://xx.xxx.xxx.xx:8080/svbase4api/Ping?wsdl"
proxy "http://xxxxxxxxxxx:xxxxxxxxx#us-east-1-static-brooks.quotaguard.com:9293"
end
response = ping_request.call(:ping, message: {message: "oogly boogly"})
puts response.to_hash[:ping_response][:return]
The puts statement does exactly what I want. It puts "saved ping message oogly boogly"
Here's my Rails model:
class Debitcard < ActiveRecord::Base
def self.ping
ping_request = Savon.client do
wsdl "http://xx.xxx.xxx.xx:8080/svbase4api/Ping?wsdl"
proxy "http://xxxxxxxxxxx:xxxxxxxxx#us-east-1-static-brooks.quotaguard.com:9293"
end
response = ping_request.call(:ping, message: {message: "oogly boogly"})
puts response.to_hash[:ping_response][:return]
#ping_response = response.to_hash[:ping_response][:return]
end
end
And this is the result in the rails server when I press a button which posts to the controller action which calls the ping method:
D, [2014-10-23T18:38:08.587540 #2200] DEBUG -- : HTTPI GET request to
xx.xxx.xxx.xx (net_http) Completed 500 Internal Server Error in 75228ms
Errno::ETIMEDOUT (Operation timed out - connect(2)):
Can anyone shine a light on this? Thanks!

Error handlers don't run in modular Sinatra app

I have a Sinatra application that uses the modular style. Everything works fine apart from my error handler blocks which don't get invoked. Here's the relevant code:
app.rb
require_relative './routes/base'
require_relative './routes/routing'
module Example
class App < Sinatra::Application
use Routes::Base
use Routes::Routing
end
end
base.rb
require 'sinatra/base'
module Example
module Routes
class Base < Sinatra::Application
configure do
# etc.
end
# Error pages.
error 404 do # <- Doesn't get invoked.
erb :not_found
end
error 500 do # <- Doesn't get invoked.
erb :internal_server_error
end
end
end
end
routing.rb
module Example
module Routes
class Routing < Base
get '/?' do
erb :home
end
end
end
end
Why don't my error handlers work?
Thanks in advance.
The use method is for adding middleware to an app, you can’t use it to compose an app like this.
In your example you actually have three different Sinatra applications, two of which are being run as middleware. When a Sinatra app is run as middleware then any request that matches one of its routes is handled by that app, otherwise the request is passed to the next component in the Rack stack. Error handlers will only apply if the request has been handled by the same app. The app that you have defined the error handlers in has no routes defined, so all requests will be passed on down the stack — the error handlers will never be used.
One way to organise a large app like this would be to simply use the same class and reopen it in the different files. This other question has an example that might be useful: Using Sinatra for larger projects via multiple files.

Can not access response.body inside after filter block in Sinatra 1.0

I'm struggling with a strange issue. According to http://github.com/sinatra/sinatra (secion Filters) a response object is available in after filter blocks in Sinatra 1.0. However the response.status is correctly accessible, I can not see non-empty response.body from my routes inside after filter.
I have this rackup file:
config.ru
require 'app'
run TestApp
Then Sinatra 1.0.b gem installed using:
gem install --pre sinatra
And this is my tiny app with a single route:
app.rb
require 'rubygems'
require 'sinatra/base'
class TestApp < Sinatra::Base
set :root, File.dirname(__FILE__)
get '/test' do
'Some response'
end
after do
halt 500 if response.empty? # used 500 just for illustation
end
end
And now, I would like to access the response inside the after filter. When I run this app and access /test URL, I got a 500 response as if the response is empty, but the response clearly is 'Some response'.
Along with my request to /test, a separate request to /favicon.ico is issued by the browser and that returns 404 as there is no route nor a static file. But I would expect the 500 status to be returned as the response should be empty.
In console, I can see that within the after filter, the response to /favicon.ico is something like 'Not found' and response to /test really is empty even though there is response returned by the route.
What do I miss?
The response.body is set Sinatra::Base#invoke, which wraps around Sinatra::Base#dispatch!, which in turn calls the filters. However, #invoke sets the response body after dispatch! is done, therefore the body is not yet set. What you want to do is probably better solved with a rack middleware.

Resources