According to rubygems.org
It handles incoming HTTP requests
A brief overview
Asking a server to:
such as http://google.com
Three components:
Metadata such as
The requested resource
I don't build ruby web frameworks
class HelloWorld def call(env) [200, {'Content-Type' => 'text/plain'}, ['Hello World']] end end
Rack::Handler::Thin.run HelloWorld.new
class Env def call(env) [200, {'Content-Type' => 'text/plain'}, env.inspect ] end end
{"SERVER_SOFTWARE"=>"thin 1.5.1 codename Straight Razor", "SERVER_NAME"=>"localhost", "rack.input"=>#<StringIO:0x007f93aa2ff2e8>, "rack.version"=>[1, 0], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>false, "rack.multiprocess"=>false, "rack.run_once"=>false, "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/", "PATH_INFO"=>"/", "REQUEST_URI"=>"/", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:5000", "HTTP_CONNECTION"=>"keep-alive", "HTTP_CACHE_CONTROL"=>"max-age=0", "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8,fr;q=0.6", "HTTP_COOKIE"=>"mp_030bc1ac25c5803c2b9d87c2c4231871_mixpanel=%7B%22distinct_id%22%3A%20%221448de3ffeb4c-0c99dee4c-1a10435d-13c680-1448de3ffecac4%22%2C%22%24initial_referrer%22%3A%20%22http%3A%2F%2Flocalhost%3A5000%2F%3Freturn_to%3D%2Fapp%2Fhome%22%2C%22%24initial_referring_domain%22%3A%20%22localhost%3A5000%22%7D", "GATEWAY_INTERFACE"=>"CGI/1.2", "SERVER_PORT"=>"5000", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "rack.url_scheme"=>"http", "SCRIPT_NAME"=>"", "REMOTE_ADDR"=>"127.0.0.1", "async.callback"=>#<Method: Thin::Connection#post_process>, "async.close"=>#<EventMachine::DefaultDeferrable:0x007f93aa30f008>}
That's super low level. Can't I just write this with Rails/Sinatra?
Rack apps can be chained together
class Analytics CODE = File.open('analytics.js') def initialize @app = HelloWorld.new end def call(env) status, headers, body = @app.call(env) body = append_code_to(body) [status, headers, body] end private def append_code_to(body) # append CODE to body end end
... but hardcoding dependencies is brittle
class Analytics def initialize(app) @app = app end # other methods end
hello_world = HelloWorld.new goodbye_world = GoodbyeWorld.new # stack: analytics, hello world Rack::Handler::Thin.run Analytics.new(hello_world) # stack: analytics, goodbye world Rack::Handler::Thin.run Analytics.new(goodbye_world)
Each app gets a chance to:
Spam trap that is more user friendly than a CAPTCHA
class Honeypot def initialize(app) @app = app end def call(env) if spambot_submission?(env) # return an empty response if there the hidden field was filled [200, {'Content-Type' => 'text/html', "Content-Length" => "0"}, []] else # send the request to the next app in the chain # it returns [status, headers, body] status, headers, body = @app.call(env) # append hidden field to body body = insert_honeypot(body) # return status, headers and modified body [status, headers, body] end end private def spambot_submission?(env) # check if hidden field was filled end def insert_honeypot(body) # append hidden field to all forms end end
my_stack = Rack::Builder.new do use Analytics run HelloWorld.new end Rack::Handler::Thin.run my_stack
converts the supplied rack config file to an instance of Rack::Builder and runs it
# config.ru use Analytics run HelloWorld.new
$ rackup # loads config.ru $ rackup my_app.ru
You can run a rails server with rackup
# config.ru require ::File.expand_path('../config/environment', __FILE__) run MyApp::Application
$ rake middleware use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ff01d83b688> use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport run Middleware::Application.routes
config.middleware.use Honeypot
config.middleware.insert_after ActiveRecord::QueryCache, Honeypot
# routes.rb # rack app mount HelloWorld => '/hello_world' # sinatra app mount Resque::Server => '/resque'
# routes.rb my_stack = Rack::Builder.new do use Analytics run HelloWorld end mount my_stack => '/hello_world'