Passing options to rackup via a Sinatra application - ruby

I'm new to ruby, learning Sinatra. While creating a Sinatra site by requiring 'sinatra' and setting up the routes directly under is pretty easy and rather well documented, creating an application by requiring 'sinatra/base' and writing a class that inherits from 'Sinatra::Base', while still relatively easy, is very poorly documented (maybe because it's a pretty recent feature of Sinatra).
And that's exactly what I am doing. I am not having too much trouble on the Sinatra part, however I am having a bit of trouble on the rackup/thin/server part. Apparently there are two ways to deploy the application: using Sinatra itself (using the run! method) and using a rackup file (typically config.ru).
Using Sinatra's run! method is extremely intuitive and works like a charm, but apparently it doesn't work if I want to deploy my app on heroku. As a matter of fact, almost all the Sinatra apps that I have encountered on GitHub use a config.ru file.
Using a rackup file might be equally intuitive, but I can't manage to understand how to pass options from the Sinatra app to the server (ir: the port). I tried to merge options to rackup's default options array:
MyApp::App.default_options.merge!(
:run => false,
:env => :production,
:port => 4567
)
run MyApp::App
by adding options directly to the app:
MyApp::App.set :port, 4567
MyApp::App.set :run, false
MyApp::App.set :env, :production
run MyApp::App
by setting options from within the application class:
module MyApp
class App < Sinatra::Base
set :port, 4567
set :run, false
set :env, :production
# ...
# config.ru
require 'app'
run MyApp::App
All the methods above failed, either by showing error messages or by just not taking any of the options into consideration. So is there any way to pass options to rackup/thin/the sever via a Sinatra app when using a rackup file? Or the options in questions should be passed directly to rackup/thin/the sever via command-line options?
As a reference to the problem, here is the little Sinatra application I am building: https://github.com/AzizLight/Wiki/

You're actully going to pass options to thin on the command line directly or via a configuration file. See all options:
$ thin -h
For production, use a configuration file:
$ thin -C thin-production.yml -R config.ru start
Here is an example thin-production.yml file:
---
address: localhost
port: 3020
servers: 4
max_conns: 1024
max_persistent_conns: 512
timeout: 30
environment: production
pid: tmp/pids/thin-production.pid
log: log/thin-production.log
daemonize: true

I know I'm resurrecting an ancient question here, but I came across another useful solution not yet mentioned. As stated in this rack wiki tutorial:
the first line starting with #\ is treated as if it was options, allowing rackup arguments to be specified in the config file.
So if you wanted to set your host to 0.0.0.0 and port to 5656, you would add the following line to the beginning of your config.ru file:
#\ -o 0.0.0.0 -p 5656

Related

Disable Sinatra standard output

For security reasons I don't wish to have Sinatra print every URL its requested in standard output, I've tried using set :logging, false as suggested in this answer using:
class SweetAppName< Sinatra::Base
set :show_exceptions, false
set :environment, :production
set :logging, false
However when I run the app using rackup and thin, I still see the request logged to the terminal:
127.0.0.1 - - [26/May/2015:09:32:34 -0700] "GET /not-a-real-url HTTP/1.0" 404 - 0.0452
How can I turn these off?
If you start your app with rackup, Rack will add some middleware, including logging. You can prevent this by using the quiet option (-q or --quiet) to rackup, i.e. from the command line:
$ rackup -q
You can include this option in your config.ru if you want, so you don’t have to remember typing it every time you start your app. The first line that starts with #\ is parsed as options, so you can have a config.ru like this:
#\ --quiet
# other middleware etc...
run SweetAppName
If you use the classic Sinatra app style you will need to add the set :logging, false line, otherwise Sinatra will add its own logging. With the modular style (like you are using in the question) this setting defaults to false so you shouldn’t need it.

Using a rackup file to custom configure an application instance

tl;dr How can I get a single Sinatra app to start up very differently on different servers via customizations to config.ru?
Background
I have a single web application written using Sinatra that's run on different servers. Currently the codebase for these servers is forked because there are some non-trivial differences in the way (discrete) parts of them work. For example:
one server authenticates users via an intranet LDAP server, while another server uses a simpler local database table lookup.
one server uses an external cron job to periodically update some statistics, while another (Windows-based) server uses an internal sleepy Thread.
one server stores certain metadata in a local table, while another server pulls the metadata from an external Wiki via screen scraping (!).
…and so on.
I'd like to get these code bases completely shared (single Git repo). I envision that each server would have one slightly-differing configuration file that causes the app to be started up differently.
Abandoned Solutions
I could change the behavior of the app based on environment variables. As there are a not-tiny number of variations in behavior, I'd rather not hide the settings in environment variables.
I could create my own "server-settings.rb" file that is unique to each machine, require it in my app.rb, and then change the configuration there. However, this seems to possibly be re-inventing the wheel. I already have a file named config.ru for each server. Shouldn't I be using this?
The Current Code
My config.ru for the app currently is simply:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new
And the app.rb that it requires is, in essence:
require 'sinatra'
require_relative 'helpers/login' # customized for LDAP lookup on this server
class MyApp < Sinatra::Application
use Rack::Session::Cookie, key:'foo.bar', path:'/', secret:'ohnoes'
set :protection, except: [:path_traversal, :session_hijacking]
configure :production do
# run various code that depends on server settings, e.g.
Snapshotter.start # there is no cron on this machine, so we do it ourselves
end
configure :development do
# run various code that depends on server settings
end
end
The Question
I'd like to make config.ru live up to its name, and have it look something like this:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new( auth: :ldap, snapshot:false, metadata: :remote_wiki, … )
How can I modify my application to change its configuration behavior based on settings supplied via config.ru? Or is this an abuse of config.ru, trying to use it for totally the wrong thing?
As soon as I started reading the question the first answer to pop into my head was "environment variable" but you scotched that straight away :)
I'll go with a mixture of one of your coulds and the desired outcome code, as it's how I structure things…
Because I want to be able to test my applications more easily, I take most of the Ruby out of the config.ru and into a separate config.rb file and leave config.ru to be a bootstrap file. So my standard skel is:
config.ru
# encoding: UTF-8
require 'rubygems'
require 'bundler'
Bundler.setup
root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )
# everything was moved into a separate module/file to make it easier to set up tests
map "/" do
run APP_NAME.app
end
app/config.rb
# encoding: utf-8
require_relative File.expand_path(File.join File.dirname(__FILE__), "../lib/ext/warn.rb")
require_relative "./init.rb" # config
require_relative "./main.rb" # routes and helpers
require 'encrypted_cookie'
# standard cookie settings
COOKIE_SETTINGS = {
:key => 'usr',
:path => "/",
:expire_after => 86400, # In seconds, 1 day
:secret => ENV["LLAVE"],
:httponly => true
}
module APP_NAME # overall name of the app
require 'rack/ssl' # force SSL
require 'rack/csrf'
if ENV["RACK_ENV"] == "development"
require 'pry'
require 'pry-nav'
end
# from http://devcenter.heroku.com/articles/ruby#logging
$stdout.sync = true
ONE_MONTH = 60 * 60 * 24 * 30
def self.app
Rack::Builder.app do
cookie_settings = COOKIE_SETTINGS
# more security if in production
cookie_settings.merge!( :secure => true ) if ENV["RACK_ENV"] == "production"
# AES encryption of cookies
use Rack::Session::EncryptedCookie, cookie_settings
if ENV["RACK_ENV"] == "production"
use Rack::SSL, :hsts => {:expires => ONE_MONTH}
end
# to stop XSS
use Rack::Csrf, :raise => true unless ENV["RACK_ENV"] == "test"
run App # the main Sinatra app
end
end # self.app
end # APP_NAME
The initial reason I did this was making it easy to run the app in specs:
shared_context "All routes" do
include Rack::Test::Methods
let(:app){ APP_NAME.app }
end
but it makes sense to me to keep this code with the rest of the application code, so to speak, as I can bundle things together, run other apps etc. I've used this to conditionally load different examples into the specs in a few projects (it helps cut down on duplicated effort and check the examples really work), so I don't see why you couldn't use it to conditionally load configurations.
This way you get to choose to use a conditional in the config.ru as to which config.rb file you would use, or use an env var in the config.rb as to which definiton of self.app to use , or pass in an options hash to self.app…
With your set up I'd rename the APP_NAME module to MyApp, and the Sinatra class to App (because quite often I'll have an website that runs a front end and an API, so the Sinatra classes get named by their function (App, API etc) and wrapped in a module named after the site) and end up with:
config.ru
map "/" do
run MyApp.app( auth: :ldap, snapshot:false, metadata: :remote_wiki )
end
config.rb
def self.app( opts={} )
opts = DEFAULT_OPTIONS.merge opts
# …
run App
end
It'll be interesting to see how other people tackle this.

How to make Thin run on a different port?

I've a very basic test app. When I execute this command the server ignores the port I specify and runs Thin on port 4567. Why is the port I specify ignored?
$ruby xxx.rb start -p 8000
== Sinatra/1.3.3 has taken the stage on 4567 for production with backup from Thin
>> Thin web server (v1.4.1 codename Chromeo)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
xxx.rb file
require 'Thin'
rackup_file = "config.ru"
argv = ARGV
argv << ["-R", rackup_file ] unless ARGV.include?("-R")
argv << ["-e", "production"] unless ARGV.include?("-e")
puts argv.flatten
Thin::Runner.new(argv.flatten).run!
config.ru file
require 'sinatra'
require 'sinatra/base'
class SingingRain < Sinatra::Base
get '/' do
return 'hello'
end
end
SingingRain.run!
#\ -p 8000
put this at the top of the config.ru
Your problem is with the line:
SingingRain.run!
This is Sinatra’s run method, which tells Sinatra to start its own web server which runs on port 4567 by default. This is in your config.ru file, but config.ru is just Ruby, so this line is run as if it was in any other .rb file. This is why you see Sinatra start up on that port.
When you stop this server with CTRL-C, Thin will try to continue loading the config.ru file to determine what app to run. You don’t actually specify an app in your config.ru, so you’ll see something like:
^C>> Stopping ...
== Sinatra has ended his set (crowd applauds)
/Users/matt/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/builder.rb:129:in `to_app': missing run or map statement (RuntimeError)
from config.ru:1:in `<main>'
...
This error is simply telling you that you didn’t actually specify an app to run in your config file.
Instead of SingingRain.run!, use:
run SingingRain
run is a Rack method that specifies which app to run. You could also do run SingingRain.new – Sinatra takes steps to enable you to use just the class itself here, or an instance.
The output to this should now just be:
>> Thin web server (v1.4.1 codename Chromeo)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:8000, CTRL+C to stop
You don’t get the == Sinatra/1.3.2 has taken the stage on 4567 for production with backup from Thin message because Sinatra isn’t running its built in server, it’s just your Thin server as you configured it.
in your config.ru add
set :port=> 8000
Also i would highly suggest using Sinatra with something like passenger+nginx which makes deploying to production a breeze. But You need not worry about this if you are going to deploy to heroku.

How to designate a Rack handler

Rackup is successfully running any Rack app via Rack's default handler. e.g.:
class RackApp
def call(environment)
[
'200',
{'Content-Type' => 'text/html'},
["Hello world"]
]
end
end
run RackApp.new
But rackup is giving "NoMethodError at / undefined method `call' for nil:NilClass" when the last line is changed to instead use Rack's built-in CGI handler:
Rack::Handler::CGI.run RackApp.new
The same objection is raised for Rack's other built-in handlers. e.g. Rack::Handler::Thin, Rack::Handler::FastCGI, even Rack::Handler::WEBrick (which is the handler Rack selects above in default mode).
What's the correct syntax here?
The rackup command reads the config file and starts a server. The Rack::Handler::XXX.run methods also start a server, independently of the rackup command (CGI is slightly different as it isn't actually a server as such).
What happens when you change the line
run RackApp.new
to
Rack::Handler::CGI.run RackApp.new
and run rackup is as follows. The server starts and parses the config file. When the Rack::Handler::CGI.run RackApp.new line is reached it is executed as any other Ruby code would be. In the case of the CGI handler this calls the app and writes the output to the standard output as it would if running as a CGI script (have a look at your terminal when you run rackup). Afterwards the 'rackup' server is started as normal, but without an app to run. When you try to access the page you'll get the NoMethodError, since the app is nil.
Using Rack::Handler::Thin is similar, but in this case, as Thin actually is a web server, it is started and will serve RackApp, but listens on Thin's default port of 8080 (not the rack default of 9292). After stopping Thin (e.g. with Ctrl-C) the default rackup server (Mongrel or Webrick) will start listening on port 9292, again with no app specified so you'll get the NoMethodError.
If you run your modified 'config.ru' as a plain Ruby script rather than using rackup you'll see the same behaviour, but without the rackup server being started. (You'll need to require rack first, so use ruby -rrack config.ru). In the CGI case the output of a single call to your app will be printed to the console, in the Thin case Thin will be started serving your app.
In order to specify the server to use with rackup, you can use the -s option, e.g. rackup -s thin will start the app using Thin (this time on the rackup default port of 9292). You can also do rackup -s cgi but this won't really work in any useful way - it just prints out the html of an error page to the console.
CGI
If you're trying to run your app as a CGI there are a couple of options. You need to create a CGI script that calls your app using the CGI handler. This could itself be a ruby script that calls Rack::Handler::CGI.run directly, in fact you could use your modified config.ru directly (you might want to rename it first and add an explicit require 'rack' line).
Alternatively you can use a shell script which then calls rackup config.ru. In this situation rackup detects that it's running as CGI and automatically uses the correct handler
http://guides.rubyonrails.org/rails_on_rack.html (see point 3.5)
also interesting:
http://railscasts.com/episodes/151-rack-middleware
http://railscasts.com/episodes/222-rack-in-rails-3

Running Sinatra on port 80

I installed Sinatra and it works but it uses port 4567 by default. I want it to run on port 80.
In an effort to get it to work on port 80, I tried this:
require 'rubygems'
require 'rack/handler/webrick'
require 'sinatra'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production,
:port => 80
)
get '/' do
"Hello World"
end
But I get this error:
$ ruby -rubygems index.rb
index.rb:5:in `<main>': undefined method `default_options' for Sinatra::Application:Class (NoMethodError)
Any idea what's going on?
Can't you just use (http://www.sinatrarb.com/configuration.html):
set :port, 80
Note that in order to bind a socket to port 80, you'll need to have superuser privileges.
And, by the way,
Using Sinatra.default_options to set base configuration items is obsolete
From: http://www.sinatrarb.com/one-oh-faq
An alternate way to accepted answer
rvmsudo rackup -p 80
In case one is using RVM to manage Ruby versions, you may not be able to use sudo that easily (or else would need to setup ruby in path).
Any port below 1024 is for privileged processes only. You'd have to run as root to run the sinatra app directly on 80. You could reverse proxy - http://sinatra-book.gittr.com/#deployment.
Yes, running anything other than Apache, Nginx, Varnish or HAProxy or port 80 is in my opiniona dangerous game. Those tools are very good at what they do. A reverse proxy setup is the way to go.

Resources