8 minutes on
Rack
Dan Webb (dan@danwebb.net)
Web Framework
Web Server
Library
A Convention
If you have a
Ruby object...
that has a call method which
takes one argument...
app.call(env)
and that method returns an
array with 3 elements...
[200, { 'Content-Type' => 'text/plain' }, 'Hello World!']
then you can connect it to any
web server that supports Rack
require 'thin'
Rack::Handler::Thin.run(app, :Port => 4000)
and you've got
yourself a web
application
Fin
app = Proc.new do |env|
[200, { 'Content-Type' => 'text/plain' },
'Hello World!']
end
require 'rubygems'
require 'thin'
Rack::Handler::Thin.run(app, :Port => 4000)
class HelloWorld
def initialize(name)
@name = name
end
def call(env)
[200, { 'Content-Type' => 'text/plain' },
"Hello #{@name}!"]
end
end
require 'rubygems'
require 'rack'
Rack::Handler::Mongrel.run(HelloWorld.new("Dan"),
:Port => 4000)
// Courtesy of Pratik Naik!
#include "ruby.h"
VALUE method_call(VALUE self, VALUE env) {
VALUE response = rb_ary_new();
VALUE headers = rb_hash_new();
rb_hash_aset(headers,
rb_str_new2("Content-Type"), rb_str_new2("text/plain"));
rb_ary_push(response, INT2NUM(200));
rb_ary_push(response, headers);
rb_ary_push(response, rb_str_new2("Hello World!"));
return response;
}
void Init_rock() {
VALUE Rock = rb_define_class("Rock", rb_cObject);
rb_define_method(Rock, "call", method_call, 1);
}
// run.rb
require 'rock'
require 'thin'
Rack::Handler::Thin.run Rock.new, :Port => 4000
def call(env)
{
"SERVER_NAME"=>"localhost",
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate",
"HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-
GB; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4",
"PATH_INFO"=>"/",
"SCRIPT_NAME"=>"",
"SERVER_PROTOCOL"=>"HTTP/1.1",
"HTTP_ACCEPT_LANGUAGE"=>"en-gb,en;q=0.5",
"HTTP_HOST"=>"localhost:4000",
"REMOTE_ADDR"=>"127.0.0.1",
"HTTP_KEEP_ALIVE"=>"300",
"REQUEST_PATH"=>"/",
"SERVER_SOFTWARE"=>"thin 0.8.2 codename Double Margarita",
"HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7",
"HTTP_VERSION"=>"HTTP/1.1",
"REQUEST_URI"=>"/",
"SERVER_PORT"=>"4000",
"QUERY_STRING"=>"",
"GATEWAY_INTERFACE"=>"CGI/1.2",
"HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,
*/*;q=0.8",
"HTTP_CONNECTION"=>"keep-alive",
"REQUEST_METHOD"=>"GET"
}
[200, { 'Content-Type' => 'text/plain' }, "Hello #{@name}!"]
Status Code
[200, { 'Content-Type' => 'text/plain' }, "Hello #{@name}!"]
HTTP Headers
[200, { 'Content-Type' => 'text/plain' }, "Hello #{@name}!"]
Response Body
Response body can be any
object that respond_to?(:each)
file = File.new('myfile.xml')
[200, { 'Content-Type' => 'application/xml' }, file]
class StreamingFile
def initialize(file)
@file = file
end
def length
File.size(@file)
end
def last_modified
File.mtime(@file).rfc822
end
def each
File.open(@file, "rb") do |file|
while part = file.read(8192)
yield part
end
File.delete(@file)
end
end
[200, {
'Content-Type' => 'audio/mp3',
'Content-Length' => file.length.to_s
}, file]
Duck typing!
• Streaming
• Clean up after response sent
• Complex responses
• Loads more...
Why?
Common interface
• Passenger
• Mongrel
• CGI
• SCGI
• FastCGI
• Thin
• Ebb
• Fuzed
• Webrick
• Litespeed
Write once,
serve however...
Convienient way
to write
micro apps
Example:
Development
Server
class StaticOrRedirect
def initialize(options={})
@redirect_base = options[:redirect_base]
root = options[:root] || Dir.pwd
@file_server = Rack::File.new(root)
end
def call(env)
path = env["PATH_INFO"]
resp = @file_server.call(env)
if resp.first == 404
[302, {
'Content-Type' => 'text/plain',
'Location' => "#{@redirect_base}#{env['PATH_INFO']}"
}, 'Not here!']
else
resp
end
end
end
Rack::Handler::Thin.run(
StaticOrRedirect.new( :redirect_base => 'http://bbc.co.uk' )
)
There's more...
• The Rack Gem
• Middleware
• Rack and Passenger
• rackup
rack.rubyforge.org
danwebb.net
Questions?

8 Minutes On Rack