How To Run EventMachine and Serve Pages In Sinatra? - ruby

I'm building a Sinatra app that uses TweetStream (which listens for Tweets using EventMachine). I would also like the app to serve pages like a normal Sinatra app but it seems like Sinatra can't "listen" for page requests when it's "listening" for Tweets.
Is this something I can fix by using a different server or structuring my app in a different way? I've tried using WebBrick and Thin.
Here is basically what I'm doing:
class App < Sinatra::Base
# listening for tweets
#client = TweetStream::Client.new
#client.track(terms) do |status|
# do some stuff when I detect terms
end
get '/' do
"Here's some page content!"
end
end

You can mount Sinatra apps within eventmachine (providing you you a awebserver that supports EM i.e., Thin). You should then have full access to the EM reactor loop from your Sinatra app, as well as allowing any other EM plugins to run too.
The Sinatra recipes have a good example:
http://recipes.sinatrarb.com/p/embed/event-machine
here is a very stripped down version of the code:
require 'eventmachine'
require 'sinatra/base'
require 'thin'
def run(opts)
EM.run do
server = opts[:server] || 'thin'
host = opts[:host] || '0.0.0.0'
port = opts[:port] || '8181'
web_app = opts[:app]
dispatch = Rack::Builder.app do
map '/' do
run web_app
end
end
unless ['thin', 'hatetepe', 'goliath'].include? server
raise "Need an EM webserver, but #{server} isn't"
end
Rack::Server.start({
app: dispatch,
server: server,
Host: host,
Port: port
})
end
end
class HelloApp < Sinatra::Base
configure do
set :threaded, false
end
get '/hello' do
'Hello World'
end
get '/delayed-hello' do
EM.defer do
sleep 5
end
'I\'m doing work in the background, but I am still free to take requests'
end
end
run app: HelloApp.new

if you really want to use the tweets streaming feature then you need to run the streaming part as a separate process and write it's results say into a database, then read those records from your sinatra app.
that's how it works the twitter stream listener is a separate thing from your sinatra app and you need some sort of a queue to join them, say redis or db, or something like that.

Related

Intercept WEBrick request

I have a web app that runs on different pieces of hardware, that for the most part consists of smart TVs and set-top boxes.
My web app contains a ruby script to setup the app for local debugging. This script builds my app, listens for file changes, and hosts the app using a simple WEBrick server.
Now I'm running into a problem on a specific piece of hardware. This hardware expects to get a success response from a POST request to a health_check API running on the same host as the web app, before it will load up the web app.
I'm simply hoping to intercept this request and spoof it so that the hardware will load my client. So far I've gotten as far as this:
def start_server
require 'webrick'
root = File.expand_path 'public'
request_callback = Proc.new { |req, res|
if req.path =~ /health_check/
# return 200 response somehow?
end
}
server = WEBrick::HTTPServer.new :Port => 5000, :DocumentRoot => root, :RequestCallback => request_callback
server.start
end
I can modify the response object to set status to 200, but it still ends up returning a 404.
You don't need to "intercept" all requests and check for a specific path. You simply want mount_proc, to handle a specific route with a proc.
Add the following before server.start:
server.mount_proc '/health_check' do |req, res|
res.body = 'what what' # your content here
end
You'll probably want to wrap this in a check to determine if you're running on whatever custom hardware requires this behavior.
See Custom Behavior in the WEBrick docs.

Modular Sinatra app returns 404's under Passenger

I have a modular Sinatra app which runs fine when executed with rackup. The config.ru file is defined as follows:
map '/' do
run My::Controllers::Default
end
map '/api' do
run My::Controllers::Api
end
When I run the app under nginx/passenger I get nothing but 404's, even for the '/' route. Suspecting that something was wrong with routing, I modified config.ru as follows:
run My::Controllers::Default
After restarting nginx, I was served the default page of the app. However, the default page of the app reaches into the api route to get some documentation to display, and that part returns a 404. Given that config.ru is able to run the Default controller, I'm sure that the issue has nothing to do with being able to load all of the relevant ruby files--which seems to be the problem in other related questions I've found on SO.
With that in mind I modified config.ru as follows:
map '/api' do
run My::Controllers::Api
end
run My::Controllers::Default
At this point I'm back to getting nothing but 404's, even for the '/' route. It seems that the map statement is confusing the webserver and making it unable to find the correct routes.
If I just run the app using rackup everything behaves as expected, so I'm really at a loss to explain what I'm seeing.
I remember this being the answer. Let me know if it works for you. If it does I'll "Accept" the answer so that others will find it.
Middleware
A bug in passenger prevents it from understanding the map statement in config.ru https://groups.google.com/forum/#!msg/phusion-passenger/PoEEp9YcWbY/1y0QL_i3tHYJ
class PassengerFix
def initialize(app)
#app = app
end
def call(env)
env["SERVER_NAME"] = env["HTTP_HOST"]
return #app.call(env)
end
end
config.ru
configure do
use PassengerFix
end

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.

Ruby cgi needs to reload apache for new value?

I have phusion-passenger installed with apache on Ubuntu. In my config.ru, I have the following code:
require 'cgi'
$tpl = CGI.new['myvar'] + '.rb'
app = proc do |env|
[200, { "Content-Type" => "text/html" }, [$tpl]]
end
run app
So then when I go to my browser at http://localhost/?myvar=hello, I see the word hello printed out, which is fine. Then I change the url to http://localhost/?myvar=world, but the page still shows hello. Only after I reload apache will the page show world.
Before using phusion-passenger, I was using mod_ruby with apache. If I remember correctly, I didn't need to restart apache to get the CGI variable to print the updated value.
I'm not stuck on needing to use CGI. I just want to be able to grab query string parameters without having to reload apache each time.
I'm not using rails or Sinatra because i'm just trying to wrap my head around the Ruby language and what phusion-passenger with apache is all about.
IMO this behavior makes sense. Because $tpl is set only once when the file is loaded, what happens when the first request is served. After that - in following requests - only the proc is called, but that does not change $tpl anymore.
Instead of using plain CGI, I would do it with a very simple Rack app:
require 'rack'
require 'rack/server'
class Server
def self.call(env)
req = Rack::Request.new(env)
tpl = "#{req.params['myvar']}.rb"
[200, {}, [tpl]]
end
end
run Server

Setting Heroku to accept traffic only from CloudFlare

I have a website deployed on Heroku (e.g. www.example.com) and I have set up CloudFlare to work as my CDN, therefore all traffic to my website goes through CloudFlare.
But I still have the link to my app on Heroku sub-domain (example.heroku.com), and if somebody tries this address, it wouldn't pass through CloudFlare anymore.
How can I hide my Heroku app address (example.heroku.com) and make my website to accept traffic only from CloudFlare?
My answer is based on the assumption that you're using Heroku to host a Ruby Rack application, since I believe that is the profile of most of Heroku's users. Otherwise, please skip.
If you're hosting a Rack app on Heroku, you could potentially insert a small piece of Rack middleware to do redirects for you.
# lib/rack/domain_redirect.rb
# encoding utf-8
# Rack Middleware that was created to handle
# autoredirecting requests away from *.herokuapp.com to
# the equivalent *.example.com. That said, it does allow you to configure
# what domain to redirect from and what domain to redirect to as well
module Rack
class DomainRedirect
attr_accessor :redirect_from_domain, :redirect_to_domain
def initialize(app, redirect_from_domain = "herokuapp.com", redirect_to_domain = "example.com")
self.redirect_from_domain = redirect_from_domain
self.redirect_to_domain = redirect_to_domain
#app = app
end
def call(env)
request = Rack::Request.new(env)
if request.host.include?(redirect_from_domain)
[301, {"Location" => request.url.sub(redirect_from_domain, redirect_to_domain)}, []]
else
#app.call(env)
end
end
end
end
Then in your config.ru
# some other middlewares and requires
require File.expand_path("../lib/rack/domain_redirect.rb", __FILE__)
use Rack::DomainRedirect
# run your app
run MyApp

Resources