Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An Introduction to Rack

An Introduction to Rack

A quick overview of Rack, including what it does, how it works, and how to use it both with Rails and standalone.

Presented at the Seattle Rails Meetup on 6 August 2014

9b0968d25731bc92a98c3e0b77e6d2ce?s=128

Benjamin Curtis

August 06, 2014
Tweet

Transcript

  1. An Introduction to Rack Benjamin Curtis / ben@honeybadger.io / @stympy

  2. A minimal API for connecting web servers and web frameworks

    What is Rack?
  3. You get an environment hash, and you return an array

    of status code, headers hash, and response body Just how minimal?
  4. Or, in other words… # my_rack_app.rb
 
 require 'rack'
 


    app = Proc.new do |env|
 ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
 end
 
 Rack::Handler::WEBrick.run app
  5. The Internet talks to your web server that talks to

    a Ruby web server that serves your application How does it work?
  6. That’s a lot of web servers! o.O

  7. Middleware But, Rails! Why do I care?

  8. It’s a Freedom Patch for the Interwebs! Middlewhat?

  9. The refresh button, it does nothing! Rack::Etag

  10. Please Hammer, don’t hurt ‘em Rack::Attack

  11. Bit conservation to fight global warming! Rack::Deflater

  12. github.com/rack/rack-contrib And many more…

  13. You can build an entire web app* with Rack But

    it’s not just middleware *For certain values of app
  14. Rack it up require './compressed_requests'
 require './key_checker'
 require './collector'
 


    use CompressedRequests
 use KeyChecker
 
 run Collector

  15. Decompress class CompressedRequests
 ValidMethods = { 'POST' => true, 'PUT'

    => true }.freeze
 ValidEncodings = { 'gzip' => true, 'deflate' => true }.freeze
 
 def initialize(app)
 @app = app
 end
 
 def call(env)
 if ValidMethods[env['REQUEST_METHOD']] && ValidEncodings[env['HTTP_CONTENT_ENCODING']]
 extracted = case env['HTTP_CONTENT_ENCODING']
 when 'gzip' then Zlib::GzipReader.new(env['rack.input']).read
 when 'deflate' then Zlib::Inflate.inflate(env['rack.input'].read)
 end
 
 env.delete('HTTP_CONTENT_ENCODING')
 env['CONTENT_LENGTH'] = extracted.length
 env['rack.input'] = StringIO.new(extracted)
 end
 
 @app.call(env)
 end
 end

  16. Authenticate class KeyChecker
 def initialize(app)
 @app = app
 end
 


    def call(env)
 @req = Rack::Request.new(env)
 env['hb.api_key'] = @api_key = @req.env['HTTP_X_API_KEY'] || @req.params['api_key']
 
 if @api_key.nil? || @api_key == ""
 return bail_with_message("Invalid API key")
 end
 @app.call(env)
 end
 
 def bail_with_message(message, code = 403)
 if @req.env['HTTP_ACCEPT'].to_s.match(%r{text/html})
 [ code, { 'Content-Type' => 'text/html' }, [ message ] ]
 else
 [ code, { 'Content-Type' => 'application/json' }, [ %Q{{"error":"#{message}"}} ] ]
 end
 end
 end

  17. Do something class Collector
 Headers = {
 html: { "Content-Type"

    => "text/html" },
 json: { "Content-Type" => "application/json" },
 }.freeze
 def self.call(env)
 req = Rack::Request.new(env)
 api_key = env['hb.api_key']
 
 case req.path_info
 when “/v1/notices" id = do_some_work(api_key, req)
 render(%Q{{"id":"#{id}"}})
 end end
 
 def self.render(body, headers = Headers[:json], status = 201)
 [ status, headers, [ body ] ]
 end
 
 end
  18. Check out a microframework… mount a Rack endpoint in your

    Rails app… go wild! Now what?
  19. Thanks! Benjamin Curtis / ben@honeybadger.io / @stympy speakerdeck.com/stympy