You are on page 1of 38

Rack in Rails 3

<http://twitter.com/rtomayko>

Ryan Tomayko
GitHub

Rack (Core Team) Rack::Contrib (Maintainer) Sinatra (Committer) Rack::Cache (Maintainer) Shotgun (Maintainer)

"Get a better logo, guys." -- Tim Bray

The Rack SPEC


<http://rack.rubyforge.org/doc/SPEC.html>

"Just" a document. Conventions for expressing HTTP in Ruby. Interface, Contract, Agreement, Protocol. Based on Python's WSGI. Fits on a single slide.

Expressing HTTP in Ruby

HTTP Request
GET/helloHTTP/1.1 Host:localhost:9292 Connection:keepalive UserAgent:Mozilla/5.0(Macinto... Accept:application/xml,applicati... AcceptEncoding:gzip,deflate,sdch AcceptLanguage:enUS,en;q=0.8 AcceptCharset:UTF8,*;q=0.5 CacheControl:maxage=0 Cookie:foo=bar

HTTP Request
GET/helloHTTP/1.1 Host:localhost:9292 Connection:keepalive UserAgent:Mozilla/5.0(Macinto... Accept:application/xml,applicati... AcceptEncoding:gzip,deflate,sdch AcceptLanguage:enUS,en;q=0.8 AcceptCharset:UTF8,*;q=0.5 CacheControl:maxage=0 Cookie:foo=bar

Rack "env" Hash


{"REQUEST_METHOD"=>"GET", "PATH_INFO"=>"/hello", "SCRIPT_NAME"=>"", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"9292", "REMOTE_ADDR"=>"127.0.0.1", "QUERY_STRING"=>"", "HTTP_HOST"=>"localhost:9292", "HTTP_CONNECTION"=>"keepalive", "HTTP_USER_AGENT"=>"Mozilla/5.0(...", "HTTP_ACCEPT"=>"application/xml,a...", "HTTP_ACCEPT_ENCODING"=>"gzip,def...", "HTTP_ACCEPT_LANGUAGE"=>"enUS,en...", "HTTP_ACCEPT_CHARSET"=>"UTF8,*;q=0.5", "HTTP_CACHE_CONTROL"=>"maxage=0", "HTTP_COOKIE"=>"foo=bar", "rack.url_scheme"=>"http", "rack.input"=>#<StringIO:0x10841ec>, "rack.errors"=>#<IO:0x3a8d4c>, "rack.version"=>[1,1], "rack.run_once"=>false, "rack.multithread"=>true, "rack.multiprocess"=>false}

HTTP Response
HTTP/1.1200OK Connection:close Date:Tue,16Feb201023:49:18GMT ContentType:text/plain ContentLength:11 CacheControl:maxage=60 HelloWorld

HTTP Response
HTTP/1.1200OK Connection:close Date:Tue,16Feb201023:49:18GMT ContentType:text/plain ContentLength:11 CacheControl:maxage=60 HelloWorld

Rack Response
[ 200, {'ContentType'=>'text/plain', 'ContentLength'=>'11', 'CacheControl'=>'maxage=60'}, ["HelloWorld"] ]

HTTP Response
HTTP/1.1200OK Connection:close Date:Tue,16Feb201023:49:18GMT ContentType:text/plain ContentLength:11 CacheControl:maxage=60 HelloWorld

Rack Response
[ 200, {'ContentType'=>'text/plain', 'ContentLength'=>'11', 'CacheControl'=>'maxage=60'}, ["HelloWorld"] ]

OR
[ 200, {'ContentType'=>'text/plain', 'ContentLength'=>File.size('hello.txt').to_s, 'CacheControl'=>'maxage=60'}, File.open('hello.txt','rb') ]

A Rack application is an Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: the status, the headers, and the body.

classHelloWorldApp defcall(env) [200, {'ContentType'=>'text/plain'}, ["HelloWorld"]] end end app=HelloWorldApp.new

classHelloWorldApp defcall(env) [200, {'ContentType'=>'text/plain'}, ["HelloWorld"]] end end app=HelloWorldApp.new

OR
app=lambda{|env| [200,{'ContentType'=>'text/plain'},["HelloWorld"]]}

We haven't used any Rack APIs yet.

Interoperability
Servers
WEBrick CGI FastCGI Mongrel Thin Passenger Unicorn Heroku

Frameworks
Rails Sinatra Merb Ramaze Camping Coset Rango Just Rack

Rack SPEC

Interoperability
Servers
WEBrick CGI FastCGI Mongrel Thin Passenger Unicorn Heroku

Frameworks
Rails Sinatra Merb Ramaze Camping Coset Rango Just Rack

Rack SPEC

Architecture of Intermediaries

HTTP Intermediaries
Alice
HTTP

Regional Proxy

Auth + Logging

Bob

Compression

HTTP

HTTP

HTTP

Load Balancing

Company Proxy

HT T

HTTP HTTP

App 1
HTTP

HTTP

HTT

HTTP

App 2

Carol

separate process inside example.com rewall

HTTP Intermediaries
Alice
HTTP

Regional Proxy

Auth + Logging

Bob

Compression

HTTP

HTTP

HTTP

Load Balancing

Company Proxy

HT T

HTTP HTTP

App 1
HTTP

HTTP

HTT

HTTP

App 2

Carol

separate process inside example.com rewall

Middleware
classShoutMiddleware definitialize(app) @app=app end defcall(env) status,headers,body=@app.call(env) parts=[] body.each{|part|parts<<part.upcase} [status,headers,parts] end end

Middleware
classShoutMiddleware definitialize(app) @app=app end defcall(env) status,headers,body=@app.call(env) parts=[] body.each{|part|parts<<part.upcase} [status,headers,parts] end end app=lambda{|env| [200,{'ContentType'=>'text/plain'},["helloworld"]]} stream=ShoutMiddleware.new(app) Rack::Handler::Mongrel.run(stream,:Port=>8080)

Rack::ETag
require'digest/md5' moduleRack classETag definitialize(app) @app=app end defcall(env) status,headers,body=@app.call(env) if!headers.has_key?('ETag') parts=[] body.each{|part|parts<<part.to_s} headers['ETag']=%("#{Digest::MD5.hexdigest(parts.join(""))}") [status,headers,parts] else [status,headers,body] end end end end

Rack Middleware
<http://github.com/rack/rack/tree/master/lib/rack>

Content Modifying
Rack::Chunked Rack::ContentLength Rack::ConditionalGet Rack::ContentType Rack::Deater Rack::ETag Rack::Head Rack::MethodOverride Rack::Runtime Rack::Sendle Rack::ShowStatus

Behavioral
Rack::CommonLogger Rack::Lint Rack::Lock Rack::Reloader

Routing
Rack::Cascade Rack::Recursive Rack::Static Rack::URLMap

Rack::Contrib
<http://github.com/rack/rackcontrib>

Rack::AcceptFormat Rack::Access Rack::Backstage Rack::Callbacks Rack::Cong Rack::Cookies Rack::CSSHTTPRequest Rack::Deect Rack::Evil Rack::HostMeta Rack::JSONP Rack::LighttpdScriptNameFix Rack::Locale

Rack::MailExceptions Rack::NestedParams Rack::NotFound Rack::ProcTitle Rack::Proler Rack::ResponseCache Rack::ResponseHeaders Rack::RelativeRedirect Rack::Signals Rack::StaticCache Rack::SimpleEndpoint Rack::TimeZone

<http://coderack.org>

<http://coderack.org>

99 Pieces of Middleware

ActionDispatch
<http://github.com/rails/rails/tree/master/actionpack/lib/action_dispatch >

Middleware extracted from ActionController ActionDispatch::MiddlewareStack Request/Response objects Routing Integration Testing Support

Default Middleware
$rakemiddleware useActionDispatch::Static useRack::Lock useRack::Runtime useRails::Rack::Logger useActionDispatch::ShowExceptions useActionDispatch::Callbacks useActionDispatch::Cookies useActionDispatch::Session::CookieStore useActionDispatch::Flash useActionDispatch::ParamsParser useRack::MethodOverride useActionDispatch::Head useActiveRecord::ConnectionAdapters::ConnectionManagement useActiveRecord::QueryCache runHelloworld::Application.routes

AD::MiddlewareStack
require'rack/cache' require'rack/contrib'

cong/application.rb

moduleHelloworld classApplication<Rails::Application #addsthenewmiddlewareatthebottomofthestack config.middleware.useRack::Cache,:verbose=>true #addsthenewmiddlewarebeforeanexistingmiddleware config.middleware.insert_beforeRack::Lock,Rack::ProcTitle #addsthenewmiddlewareafteranexistingmiddleware config.middleware.insert_afterRack::Head,Rack::CSSHTTPRequest #swaponemiddlewareforanother config.middleware.swapActionDispatch::ShowExceptions,SuperShowExceptions #removeamiddlewarefromthestack config.middleware.deleteRack::MethodOverride end end

Middleware Plugins
<http://boldr.net/upgradepluginsgemsrails3>
require"rack/cache"

rails-cache.gem/lib/rails/cache.rb

moduleCache classRailtie<Rails::Railtie railtie_name:rails_cache initializer"rails_cache.insert_rack_cache"do|app| app.config.middleware.useRack::Cache, :metastore=>"file:#{Rails.root+"cache/rack/meta"}", :entitystore=>"file:#{Rails.root+"cache/rack/body"}", :verbose=>true end end end

## Bundle the gems you use: gem "rails-cache", :require => "rails/cache"

Gemle

Rack::Mount
<http://github.com/josh/rackmount> cong.ru require'rack/mount' Routes=Rack::Mount::RouteSet.newdo|set| #add_routetakesarackapplicationandconditionstomatchwith #conditionsmaybestringsorregexps #SeeRack::Mount::RouteSet#add_routeformoreoptions. set.add_routeFooApp,:method=>'get',:path=>%{/foo} end #Theroutesetitselfisasimplerackappyoumount runRoutes

Rack::Mount
<http://github.com/josh/rackmount> cong.ru require'rack/mount' Routes=Rack::Mount::RouteSet.newdo|set| #add_routetakesarackapplicationandconditionstomatchwith #conditionsmaybestringsorregexps #SeeRack::Mount::RouteSet#add_routeformoreoptions. set.add_routeFooApp,:method=>'get',:path=>%{/foo} end #Theroutesetitselfisasimplerackappyoumount runRoutes

Rack::Mount
<http://github.com/josh/rackmount> cong.ru require'rack/mount' Routes=Rack::Mount::RouteSet.newdo|set| #add_routetakesarackapplicationandconditionstomatchwith #conditionsmaybestringsorregexps #SeeRack::Mount::RouteSet#add_routeformoreoptions. set.add_routeFooApp,:method=>'get',:path=>%{/foo} end #Theroutesetitselfisasimplerackappyoumount runRoutes

Actions = Rack Apps


$railsconsole Loadingdevelopmentenvironment(Rails3.0.0.beta) >>action=ApplicationController.action(:index) =>#<ActionController::Metal::ActionEndpoint:0x2290fd4 @action=:index, @controller=ApplicationController, @_formats=<stuff>> >>action.respond_to?(:call) =>true

Routing to Rack
<http://lindsaar.net/2010/2/7/rails_3_routing_with_rack>

classTwitterApp<Sinatra::Base set:root,File.dirname(__FILE__) get'/twitter'do @user='raasdnil' t=Twitter::Search.new(@user).fetch @tweets=t.results erb:twitter end end require'twitter_app' HelloWorldApp::Application.routes.drawdo|map| match'/twitter',:to=>TwitterApp end

lib/twitter_app.rb

cong/routes.rb

More Rack + Rails


Rack SPEC (Seriously)
<http://rack.rubyforge.org/doc/> <http://rack.rubyforge.org/doc/SPEC.html>

Rack API Documentation Rack Source (On The GitHub)


<http://github.com/rack/rack>

RailsGuides: Rails on Rack (Updated for Rails 3)


<http://guides.rails.info/rails_on_rack.html>

Scaling Rails: Rack & Metal (Screencast)


<http://railslab.newrelic.com/2009/06/05/episode-14-rack-metal>

You might also like