September 9th, 2008 cschneid
Ryan Tomayko provided a great changelist for the new gem release of Sinatra, which I hope to expand on a bit here, by pointing you to more details on the changes.
Here’s a quick summary of changes since the 0.2.2 release:
-
Add sinatra.gemspec w/ support for github gem builds. Forks can now
enable the build gem option in github to get free username-sinatra.gem
builds: gem install username-sinatra.gem — source=http://gems.github.com/
-
Require Rack 0.4; removes frozen rack dir.
-
Basic RSpec support; require ‘sinatra/test/rspec’ instead of
‘sinatra/test/spec’ to use. [avdi]
-
before filters can modify request environment vars used for
routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting
type functionality.
-
In-file templates now uses @@ instead of ## as template separator.
-
Top-level environment test predicates: development?, test?, production?
-
Top-level “set”, “enable”, and “disable” methods for tweaking
app options. [rtomayko] — This allows the syntax in my post about options
-
Top-level “use” method for building Rack middleware pipelines
leading to app. see readme for usage. [rtomayko] — this is the use syntax in my post about sessions
-
New “reload” option - set false to disable reloading in development.
-
New “host” option - host/ip to bind to [cschneid]
-
New “app_file” option - override the file to reload in development
mode [cschneid] — This makes embedding Sinatra inside other apps easier to do during development. More info in my app_file post.
-
Development error/not_found page cleanup [sr, adamwiggins]
-
Remove a bunch of core extensions (String#to_param, String#from_param,
Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass)
-
Various grammar and formatting fixes to README; additions on
community and contributing [cypher]
-
Build RDoc using Hanna template: http://sinatra.rubyforge.org/api/
-
Specs, documentation and fixes for splat’n routes [vic] — See how to use splat routes in my post on it.
-
Fix whitespace errors across all source files. [rtomayko]
-
Fix streaming issues with Mongrel (body not closed). [bmizerany]
-
Fix various issues with environment not being set properly (configure
blocks not running, error pages not registering, etc.) [cypher]
-
Fix to allow locals to be passed to ERB templates [cschneid] — More Details
-
Fix locking issues causing random errors during reload in development.
-
Fix for escaped paths not resolving static files [Matthew Walker]
Posted in Programming, Ruby, Sinatra | No Comments »
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 | 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 | No 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 | No 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 | 2 Comments »
June 12th, 2008 cschneid
Cookies are a fairly simple thing to use in Sinatra, but they have a few quirks.
Lets first look at the simple use case:
require 'sinatra'
get '/' do
# Get the string representation
cookie = request.cookies["thing"]
# Set a default
cookie ||= 0
# Convert to an integer
cookie = cookie.to_i
# Do something with the value
cookie += 1
# Reset the cookie
set_cookie("thing", cookie)
# Render something
"Thing is now: #{cookie}"
end
Setting a path, expiration date, or domain gets a little more complicated - see the source code for set_cookie if you want to dig deeper.
set_cookie("thing", { :domain => myDomain,
:path => myPath,
:expires => Date.new } )
That’s the easy stuff with cookies - It can also serialize Array objects, separating them with ampersands (&), but when they come back, it doesn’t deserialize or split them in any way, it hands you the raw, encoded string for your parsing pleasure.
I’ll look into how serialization and data handling needs to occur in a later post. I’m guessing that it’s the user’s job to split and un-encode, but I haven’t verified that yet.
Posted in Programming, Ruby, Sinatra | No Comments »
June 9th, 2008 cschneid
After much poking and prodding it looks like Sinatra won’t work on top of JRuby Rack. It’s an unintended consequence of how everything was implemented, and it looks like some (hopefully minor) changes will have to be made to Sinatra.
Steps to get to where I am:
- Install JRuby 1.1.2
- Get tomcat - I used a 6.0.14 install I had laying around
- Install the JRuby gem “warble”, which will come with jruby-rack
- Create a new directory structure, described below
- Copy jruby-complete.jar, and jruby-rack.jar to the WEB-INF/lib directory
- Create a simple Sinatra hello world app
- Create your web.xml file as described
The directory structure
jruby-sinatra/
jruby-sinatra/WEB-INF
jruby-sinatra/WEB-INF/web.xml
jruby-sinatra/WEB-INF/hello_world.rb
jruby-sinatra/WEB-INF/sinatra # full copy of sinatra goes here
jruby-sinatra/WEB-INF/lib
jruby-sinatra/WEB-INF/lib/jruby-complete-1.1.1.jar
jruby-sinatra/WEB-INF/lib/jruby-rack-0.9.jar
My (non-working) current web.xml file.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>public.root</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>rackup</param-name>
<param-value>
$:.unshift '/Users/cschneid/servers/apache-tomcat-6.0.14/webapps/jruby-sinatra/WEB-INF/'
$:.unshift '/Users/cschneid/servers/apache-tomcat-6.0.14/webapps/jruby-sinatra/WEB-INF/sinatra/lib'
puts "====================="
puts "====================="
puts "====================="
puts "Current file is: #{__FILE__}"
puts "Dollar 0 is: #{$0}"
puts "List of load paths:"
puts $:.map {|a| puts a }
puts "====================="
puts "====================="
puts "====================="
puts "Requiring sinatra"
require "sinatra"
puts "Setting up the sinatra app options"
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production
)
puts "About to require test"
require 'test'
puts "About to run sinatra"
run Sinatra.application
</param-value>
</context-param>
<filter>
<filter-name>RackFilter</filter-name>
<filter-class>org.jruby.rack.RackFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RackFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jruby.rack.RackServletContextListener</listener-class>
</listener>
</web-app>
The problem - the load fails on the Sinatra::Application.default_options.merge line. The first time you access default_options, it generates the hash. Every subsequent time, it just hands back the already created instance. Since this is the first time, it attempts to create the default_options structure. It gets a “root” of the directory tree from $0. And then later combines it with ‘/views’ and ‘/public’. The problem lies in the fact that we have a ruby script embedded inside the web.xml file. It doesn’t have a reasonable $0 to even set, so it doesn’t. If you ask for __FILE__, it just reports back with “<script>”. End result: you get a simple “can’t convert nil into String” exception thrown.
Next Steps - I don’t know, I think we should probably wean Sinatra off of $0 if at all possible. Or at least figure out how to set it to something reasonable so that default_options doesn’t blow up. If anybody has any ideas on how to go about this, let me know, since this doesn’t have an obvious fix that I see. Mainly, I don’t want to break functionality elsewhere (for plain ruby).
Posted in Programming, Ruby, Sinatra | 3 Comments »
June 8th, 2008 cschneid
I don’t like deployment via FastCGI that much. It’s just too unstable, and random for my tastes. So when I saw that Passenger was going to support Rack, I knew I had to get that working on Dreamhost.
Once I started going at it, it only took me a few minutes to make everything work. You can find documentation at the Passenger Github repository.
Steps:
- Setup your account to support mod_rails
- Create the directory structure
- Make a rackup file (config.ru)
- Create a very simple Sinatra application
1) Setting up the account
- Domains -> Manage Domains -> Edit (web hosting column)
- Enable “Ruby on Rails Passenger (mod_rails)”
- Add the public directory to the web directory box. So if you were using “gittr.com”, it would change to “gittr.com/public”
- Save your changes
2) Creating the directory structure
domain.com/
domain.com/tmp
domain.com/public
# a vendored version of sinatra - not necessary if you use the gem
domain.com/sinatra
3) Creating a rackup file
# This file goes in domain.com/config.ru
require 'sinatra/lib/sinatra.rb'
require 'rubygems'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production
)
require 'test.rb'
run Sinatra.application
4) A very simple Sinatra application
# this is test.rb referred to above
get '/' do
"Worked on dreamhost"
end
get '/foo/:bar' do
"You asked for foo/#{params[:bar]}"
end
And that’s all there is to it! Once it’s all setup, point your browser at your domain, and you should see a “Worked on Dreamhost” page. To restart the application after making changes, you need to run “touch tmp/restart.txt”.
Posted in Programming, Ruby, Sinatra, System Admin | 8 Comments »