Rack in Rails 3 <http://twitter.com/rtomayko> Ryan Tomayko - - PowerPoint PPT Presentation
Rack in Rails 3 <http://twitter.com/rtomayko> Ryan Tomayko - - PowerPoint PPT Presentation
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
Ryan Tomayko
- Rack (Core Team)
- Rack::Contrib (Maintainer)
- Sinatra (Committer)
- Rack::Cache (Maintainer)
- Shotgun (Maintainer)
<http://twitter.com/rtomayko>
GitHub
"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
GET /hello HTTP/1.1 Host: localhost:9292 Connection: keep‐alive User‐Agent: Mozilla/5.0 (Macinto... Accept: application/xml,applicati... Accept‐Encoding: gzip,deflate,sdch Accept‐Language: en‐US,en;q=0.8 Accept‐Charset: UTF‐8,*;q=0.5 Cache‐Control: max‐age=0 Cookie: foo=bar
HTTP Request
GET /hello HTTP/1.1 Host: localhost:9292 Connection: keep‐alive User‐Agent: Mozilla/5.0 (Macinto... Accept: application/xml,applicati... Accept‐Encoding: gzip,deflate,sdch Accept‐Language: en‐US,en;q=0.8 Accept‐Charset: UTF‐8,*;q=0.5 Cache‐Control: max‐age=0 Cookie: foo=bar
HTTP Request
{"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"=>"keep‐alive", "HTTP_USER_AGENT"=>"Mozilla/5.0 (...", "HTTP_ACCEPT"=>"application/xml,a...", "HTTP_ACCEPT_ENCODING"=>"gzip,def...", "HTTP_ACCEPT_LANGUAGE"=>"en‐US,en...", "HTTP_ACCEPT_CHARSET"=>"UTF‐8,*;q=0.5", "HTTP_CACHE_CONTROL"=>"max‐age=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}
Rack "env" Hash
HTTP/1.1 200 OK Connection: close Date: Tue, 16 Feb 2010 23:49:18 GMT Content‐Type: text/plain Content‐Length: 11 Cache‐Control: max‐age=60 Hello World
HTTP Response
HTTP/1.1 200 OK Connection: close Date: Tue, 16 Feb 2010 23:49:18 GMT Content‐Type: text/plain Content‐Length: 11 Cache‐Control: max‐age=60 Hello World
HTTP Response
[ 200, {'Content‐Type' => 'text/plain', 'Content‐Length' => '11', 'Cache‐Control' => 'max‐age=60'}, ["Hello World"] ]
Rack Response
HTTP/1.1 200 OK Connection: close Date: Tue, 16 Feb 2010 23:49:18 GMT Content‐Type: text/plain Content‐Length: 11 Cache‐Control: max‐age=60 Hello World
HTTP Response
[ 200, {'Content‐Type' => 'text/plain', 'Content‐Length' => '11', 'Cache‐Control' => 'max‐age=60'}, ["Hello World"] ]
Rack Response
[ 200, {'Content‐Type ' => 'text/plain', 'Content‐Length ' => File.size('hello.txt').to_s , 'Cache‐Control ' => 'max‐age=60'}, File.open('hello.txt', 'rb') ]
OR
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.
class HelloWorldApp def call(env) [200, {'Content‐Type' => 'text/plain'}, ["Hello World"]] end end app = HelloWorldApp.new
class HelloWorldApp def call(env) [200, {'Content‐Type' => 'text/plain'}, ["Hello World"]] end end app = HelloWorldApp.new app = lambda { |env| [200, {'Content‐Type'=>'text/plain'}, ["Hello World"]] }
OR
We haven't used any Rack APIs yet.
Interoperability
WEBrick CGI FastCGI Mongrel Thin Passenger Unicorn Heroku Rails Sinatra Merb Ramaze Camping Coset Rango Just Rack
Servers Frameworks
Rack SPEC
Interoperability
WEBrick CGI FastCGI Mongrel Thin Passenger Unicorn Heroku Rails Sinatra Merb Ramaze Camping Coset Rango Just Rack
Servers Frameworks
Rack SPEC
Architecture of Intermediaries
HTTP Intermediaries
Company Proxy
Alice Bob Carol
HTTP HTTP HTTP HTTP HTTP
Auth + Logging Compression
Regional Proxy
Load Balancing
App 1 App 2
HTTP HTTP HTTP HTTP HTTP HTTP
separate process inside example.com firewall
HTTP Intermediaries
Company Proxy
Alice Bob Carol
HTTP HTTP HTTP HTTP HTTP
Auth + Logging Compression
Regional Proxy
Load Balancing
App 1 App 2
HTTP HTTP HTTP HTTP HTTP HTTP
separate process inside example.com firewall
class ShoutMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) parts = [] body.each { |part| parts << part.upcase } [status, headers, parts] end end
Middleware
class ShoutMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) parts = [] body.each { |part| parts << part.upcase } [status, headers, parts] end end
Middleware
app = lambda { |env| [200, {'Content‐Type'=>'text/plain'}, ["hello world"]] } stream = ShoutMiddleware.new(app) Rack::Handler::Mongrel.run(stream, :Port => 8080)
Rack::ETag
require 'digest/md5' module Rack class ETag def initialize(app) @app = app end def call(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
Rack::Chunked Rack::ContentLength Rack::ConditionalGet Rack::ContentType Rack::Deflater Rack::ETag Rack::Head Rack::MethodOverride Rack::Runtime Rack::Sendfile Rack::ShowStatus Rack::CommonLogger Rack::Lint Rack::Lock Rack::Reloader Rack::Cascade Rack::Recursive Rack::Static Rack::URLMap
Routing Behavioral Content Modifying
<http://github.com/rack/rack/tree/master/lib/rack>
Rack::Contrib
Rack::AcceptFormat Rack::Access Rack::Backstage Rack::Callbacks Rack::Config Rack::Cookies Rack::CSSHTTPRequest Rack::Deflect Rack::Evil Rack::HostMeta Rack::JSONP Rack::LighttpdScriptNameFix Rack::Locale Rack::MailExceptions Rack::NestedParams Rack::NotFound Rack::ProcTitle Rack::Profiler Rack::ResponseCache Rack::ResponseHeaders Rack::RelativeRedirect Rack::Signals Rack::StaticCache Rack::SimpleEndpoint Rack::TimeZone
<http://github.com/rack/rack‐contrib>
<http://coderack.org>
<http://coderack.org>
99 Pieces of Middleware
ActionDispatch
- Middleware extracted from ActionController
- ActionDispatch::MiddlewareStack
- Request/Response objects
- Routing
- Integration Testing Support
<http://github.com/rails/rails/tree/master/actionpack/lib/action_dispatch >
$ rake middleware use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::Callbacks use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::MethodOverride use ActionDispatch::Head use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache run Helloworld::Application.routes
Default Middleware
AD::MiddlewareStack
require 'rack/cache' require 'rack/contrib' module Helloworld class Application < Rails::Application # adds the new middleware at the bottom of the stack config.middleware.use Rack::Cache, :verbose => true # adds the new middleware before an existing middleware config.middleware.insert_before Rack::Lock, Rack::ProcTitle # adds the new middleware after an existing middleware config.middleware.insert_after Rack::Head, Rack::CSSHTTPRequest # swap one middleware for another config.middleware.swap ActionDispatch::ShowExceptions, SuperShowExceptions # remove a middleware from the stack config.middleware.delete Rack::MethodOverride end end
config/application.rb
Middleware Plugins
require "rack/cache" module Cache class Railtie < Rails::Railtie railtie_name :rails_cache initializer "rails_cache.insert_rack_cache" do |app| app.config.middleware.use Rack::Cache, :metastore => "file:#{Rails.root + "cache/rack/meta"}", :entitystore => "file:#{Rails.root + "cache/rack/body"}", :verbose => true end end end
<http://boldr.net/upgrade‐plugins‐gems‐rails‐3 >
## Bundle the gems you use: gem "rails-cache", :require => "rails/cache"
rails-cache.gem/lib/rails/cache.rb Gemfile
Rack::Mount
require 'rack/mount' Routes = Rack::Mount::RouteSet.new do |set| # add_route takes a rack application and conditions to match with # conditions may be strings or regexps # See Rack::Mount::RouteSet#add_route for more options. set.add_route FooApp, :method => 'get', :path => %{/foo} end # The route set itself is a simple rack app you mount run Routes
<http://github.com/josh/rack‐ mount> config.ru
Rack::Mount
require 'rack/mount' Routes = Rack::Mount::RouteSet.new do |set| # add_route takes a rack application and conditions to match with # conditions may be strings or regexps # See Rack::Mount::RouteSet#add_route for more options. set.add_route FooApp, :method => 'get', :path => %{/foo} end # The route set itself is a simple rack app you mount run Routes
<http://github.com/josh/rack‐ mount> config.ru
Rack::Mount
require 'rack/mount' Routes = Rack::Mount::RouteSet.new do |set| # add_route takes a rack application and conditions to match with # conditions may be strings or regexps # See Rack::Mount::RouteSet#add_route for more options. set.add_route FooApp, :method => 'get', :path => %{/foo} end # The route set itself is a simple rack app you mount run Routes
<http://github.com/josh/rack‐ mount> config.ru
Actions = Rack Apps
$ rails console Loading development environment (Rails 3.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
class TwitterApp < 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
<http://lindsaar.net/2010/2/7/rails_3_routing_with_rack >
require 'twitter_app' HelloWorldApp::Application.routes.draw do |map| match '/twitter', :to => TwitterApp end lib/twitter_app.rb config/routes.rb
More Rack + Rails
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>
Rack API Documentation
<http://rack.rubyforge.org/doc/>
Rack SPEC (Seriously)
<http://rack.rubyforge.org/doc/SPEC.html>
Rack Source (On The GitHub)
<http://github.com/rack/rack>