July 17th, 2008 cschneid
Most web applications have some sort of admin area where users shouldn’t be. Passwording it is the obvious solution, but it isn’t obvious how to implement passwords in Sinatra. I stole this code from Ryan Tomayko’s Wink blog, and extracted it into a more generic module for reuse. Two parts are involved, the module, which I put into a separate file, and include. And then the code itself to pull it into the helpers and use it in the event handlers.
The module:
module Sinatra
module Authorization
def auth
@auth ||= Rack::Auth::Basic::Request.new(request.env)
end
def unauthorized!(realm="myApp.com")
header 'WWW-Authenticate' => %(Basic realm="#{realm}")
throw :halt, [ 401, 'Authorization Required' ]
end
def bad_request!
throw :halt, [ 400, 'Bad Request' ]
end
def authorized?
request.env['REMOTE_USER']
end
def authorize(username, password)
# Insert your logic here to determine if username/password is good
false
end
def require_administrative_privileges
return if authorized?
unauthorized! unless auth.provided?
bad_request! unless auth.basic?
unauthorized! unless authorize(*auth.credentials)
request.env['REMOTE_USER'] = auth.username
end
def admin?
authorized?
end
end
end
To use the module:
require 'authorization'
helpers do
include Sinatra::Authorization
end
get '/admin' do
require_administrative_privileges
# Do private stuff
end
EDIT: Thanks to foca on #sinatra for the nicer setup of authorize
License: Because this was extracted out of Wink, follow the Wink license (found here).
Posted in Programming, Ruby, Sinatra | 8 Comments »
July 17th, 2008 cschneid
Once of the common things needed in webapps is to escape html.
Most frameworks acknowledge that need, and include helpers to do it for you. Sinatra isn’t that kind of framework. If you want it, it’s easy to wire in, but you need to ask.
Luckily, Sinatra is easily convinced, and will point you to it’s close friend, Rack.
helpers do
include Rack::Utils
alias_method :h, :escape_html
end
These few lines will include all of Rack::Utils (a few methods), and then rename one of them to match what Rails and Merb (and others) provide.
Now in your views, you can easily call escape html like:
Posted in Programming, Sinatra | 1 Comment »
July 16th, 2008 cschneid
Over the course of about 4 hours I setup a website to do logging of #sinatra, and put it on my brand new url http://www.irclogger.com. Go check it out for back history of the #sinatra room. Hopefully it’s all google searchable too.
Future features include, but are not limited to:
- a proper header
- a sidebar with all of the external links mentioned
- multi channel logging (not limited to #sinatra)
Many thanks to sr and foca for pushing me to get features done. Nothing like peer pressure to make code fly out.
Posted in Programming | No Comments »
July 16th, 2008 cschneid
After some talk in the #sinatra IRC channel, I decided to start a proper book project to collect and collate all of the brain dumping I’ve been doing here.
Check out the git repository at http://github.com/cschneid/sinatra-book
So far the project has been progressing fairly slowly, but there is already some great content in there. I will continue to convert blog posts into chapters, and look forward to any other contributions from others.
I will be setting up a nightly build of the book, pointing at the currently unsetup sinatra-book.gittr.com.
To build it yourself in the mean time, you will need a copy of “thor”, a reasonably cool rake/sake replacement. You’ll also need maruku, the markdown library we are using to write everything.
Posted in Sinatra | 1 Comment »
June 26th, 2008 cschneid
Thanks to the requests a few enterprising commenters, I’ve looked into logging with mod_rails and Sinatra a little closer. My last attempt was close, but not quite. If you swap out the config.ru file for this version, logging will work correctly.
require 'sinatra/lib/sinatra.rb'
require 'rubygems'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production,
:raise_errors => true
)
log = File.new("sinatra.log", "a")
STDOUT.reopen(log)
STDERR.reopen(log)
require 'test.rb'
run Sinatra.application
Some interesting things to note
- Open the file with “a” instead of “w”. Append rather than open means that restarts and the like won’t reset the log file. A fun fact about passenger: it seems that passenger restarts your application if you return a 500 response code. The append means that we don’t blow away the log file right away after that.
- raise_errors => true. This is important. When Sinatra is in production mode, it just stops throwing errors, and instead shows a simple “Server Error” page. This setting re-enables the exception throwing.
At least one more thing to look into:: Nice error pages. How can we have good error logging AND good error pages?
Posted in Programming | 5 Comments »
June 25th, 2008 cschneid
A question came up on IRC today regarding Sinatra’s redirect helper. royfreeman was trying to redirect from inside a post action. Sinatra redirected the URL ok, but the new request continued to use the POST method to request the new URL. He was trying to do this from a mobile device, and was confused when the request used a GET with his laptop’s browser.
Sinatra sends a 302 response code as a redirect. According to the spec, 302 shouldn’t change the request method, but you can see a note saying that most clients do change it. Apparently the mobile browser that person was using did things correctly (instead of the mainstream misinterpretation).
The fix for this in the spec is 2 different response codes: 303 and 307. 303 resets to GET, 307 keeps the same method.
To force sinatra to send a different response code, it’s very simple:
redirect '/', 303 # forces the 303 return code
## OR:
redirect '/', 307 # forces the 307 return code
Many thanks to Sydd for working through this, he did most of the hunting to find the specification details and to figure out what was going on.
Posted in Programming, Ruby, Sinatra | 3 Comments »
June 24th, 2008 cschneid
I wrote about how to deploy a Sinatra application on top of Passenger, with specific instructions for Dreamhost. As it turns out, the logging functionality of Sinatra didn’t work with the instructions, this is the workaround.
The technical details: The trick to this is that Sinatra’s logging uses Rack’s CommonLogger class. If you don’t tell it otherwise, it logs to whatever rack’s env[‘rack.error’] is set to, which is STDERR in this case. Sinatra doesn’t give an easy way to tell it to use a certain logger, but instead, always uses the defaults. The easiest way is to ignore that whole layer off annoyance, and just reopen STDERR and STDOUT to write to the file. That way, no change to Sinatra or your application is needed.
Just add three lines to your config.ru file:
# Add these 3 lines
log = File.new("sinatra.log", "w")
STDOUT.reopen(log)
STDERR.reopen(log)
The config.ru file in it’s entirety:
require 'sinatra/lib/sinatra.rb'
require 'rubygems'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production
)
# Add these 3 lines
log = File.new("sinatra.log", "w")
STDOUT.reopen(log)
STDERR.reopen(log)
require 'test.rb'
run Sinatra.application
Posted in Programming | 2 Comments »
June 17th, 2008 cschneid
I’ve previously written about deploying Sinatra to Dreamhost, both with FastCGI, and also Passenger (mod_rails). This howto will cover how to deploy Sinatra to a load-balanced reverse-proxy setup using Lighttpd and Thin.
1) Install Lighttpd and Thin
# Figure out lighttpd yourself, it should be handled by your
# linux distro's package manager
# For thin:
gem install thin
2) Create your rackup file - the “require ‘app’” line should require the actual sinatra app you have written.
require 'sinatra'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production
)
require 'app'
run Sinatra.application
3) Setup a config.yml - change the /path/to/my/app path to reflect reality.
---
environment: production
chdir: /path/to/my/app
address: 127.0.0.1
user: root
group: root
port: 4567
pid: /path/to/my/app/thin.pid
rackup: /path/to/my/app/config.ru
log: /path/to/my/app/thin.log
max_conns: 1024
timeout: 30
max_persistent_conns: 512
daemonize: true
4) Setup lighttpd.conf - change mydomain to reflect reality. Also make sure the first port here matches up with the port setting in config.yml.
$HTTP["host"] =~ "(www\.)?mydomain\.com" {
proxy.balance = "fair"
proxy.server = ("/" =>
(
( "host" => "127.0.0.1", "port" => 4567 ),
( "host" => "127.0.0.1", "port" => 4568 )
)
)
}
5) Start thin and your application . I have a rake script so I can just call “rake start” rather than typing this in. It’s just a few lines to do that.
thin -s 2 -C config.yml -R config.ru start
Go to mydomain.com/ and see the result! Everything should be setup now, check it out at the domain you setup in your lighttpd.conf file.
Variations - nginx - I haven’t looked into the config file syntax, but there isn’t any reason that this exact same approach wouldn’t work with nginx. Thin and the rackup file stay the same, while the nginx layer has to be configured to reverse proxy to the thin install.
Variations - More Thin instances - To add more thin instances, change the -s 2 parameter on the thin start command to be how ever many servers you want. Then be sure lighttpd proxies to all of them by adding more lines to the proxy statements. Then restart lighttpd and you’re done.
Posted in Linux, Programming, Ruby, Sinatra, System Admin | 1 Comment »
June 15th, 2008 cschneid
There are several ways of getting parameters and data into your Sinatra application.
1) URL Options
get '/post/:id' do
post_id = params[:id]
end
# Several options, period gets interpreted as a separator between the two.
get '/author/:id.:format' do
author_id = params[:id]
format = params[:format]
end
# Several options, separated by '/'
get '/author/:author_id/comments/:comment_id' do
author_id = params[:author_id]
comment_id = params[:comment_id]
end
2) URL Parameters - the classic ?foo=bar&baz=quux
# this will get the post id out of /author?id=2
get '/author' do
author_id = params[:id]
end
# Works the same way for the POST method (and DELETE and PUT of course)
post '/author' do
author_id = params[:id]
end
3) Raw Posted Data - if the POSTed content is in the “application/x-www-form-urlencoded” format, it will get parsed as above, with the params[:id] method of getting the data. But occasionally, you want the whole chunk of raw data. This is useful for images, or big chunks of xml.
post '/data' do
raw = request.env["rack.input"].read
end
Posted in Programming, Ruby, Sinatra | 6 Comments »
June 13th, 2008 cschneid
I went looking for a way to embed a short little script in my Haml template, but didn’t find much in my googling. Turns out that before Haml 2.0, to do this, it requires a custom filter. Well, now Haml 2.0 ships with a built-in javascript filter.
.before content above script
:javascript
alert("Hello World");
alert("More scripting!");
.after content after script
The script gets wrapped in <script> tags, and works as expected.
Posted in Programming | 1 Comment »